diff --git a/.gitignore b/.gitignore
index 594c411c2..51d98a916 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,3 +53,6 @@ project.xcworkspace/
*.dSYM/
.DS_Store
+Platform/Windows/Build
+Platform/Windows/Win32
+Platform/Windows/x64
diff --git a/platform/Windows/eduke32.sln b/platform/Windows/eduke32.sln
index 8d9faaf28..25a48ad95 100644
--- a/platform/Windows/eduke32.sln
+++ b/platform/Windows/eduke32.sln
@@ -29,6 +29,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glad", "glad.vcxproj", "{6A
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rednukem", "rednukem.vcxproj", "{44C4B4F0-B489-4612-B9C7-A38503B7FB67}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsmackerdec", "libsmackerdec.vcxproj", "{598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nblood", "nblood.vcxproj", "{5407CB5A-4B15-41FA-B79B-8B386A14D275}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -101,6 +105,22 @@ Global
{44C4B4F0-B489-4612-B9C7-A38503B7FB67}.Release|Win32.Build.0 = Release|Win32
{44C4B4F0-B489-4612-B9C7-A38503B7FB67}.Release|x64.ActiveCfg = Release|x64
{44C4B4F0-B489-4612-B9C7-A38503B7FB67}.Release|x64.Build.0 = Release|x64
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Debug|Win32.ActiveCfg = Debug|Win32
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Debug|Win32.Build.0 = Debug|Win32
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Debug|x64.ActiveCfg = Debug|x64
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Debug|x64.Build.0 = Debug|x64
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Release|Win32.ActiveCfg = Release|Win32
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Release|Win32.Build.0 = Release|Win32
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Release|x64.ActiveCfg = Release|x64
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}.Release|x64.Build.0 = Release|x64
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Debug|Win32.ActiveCfg = Debug|Win32
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Debug|Win32.Build.0 = Debug|Win32
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Debug|x64.ActiveCfg = Debug|x64
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Debug|x64.Build.0 = Debug|x64
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Release|Win32.ActiveCfg = Release|Win32
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Release|Win32.Build.0 = Release|Win32
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Release|x64.ActiveCfg = Release|x64
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -114,6 +134,8 @@ Global
{32D4CF70-A3D6-4CEA-81D7-64C743980276} = {8BD117A0-7FA2-44B0-88A5-29D6C220601E}
{6AC1D997-8DAE-4343-8DD8-DA2A1CA63212} = {8BD117A0-7FA2-44B0-88A5-29D6C220601E}
{44C4B4F0-B489-4612-B9C7-A38503B7FB67} = {18C3CB5F-1DE6-4CFE-B7F7-ABE824222E1C}
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45} = {8BD117A0-7FA2-44B0-88A5-29D6C220601E}
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275} = {18C3CB5F-1DE6-4CFE-B7F7-ABE824222E1C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0346D2C0-3EB9-4AFB-973F-2904758D6C02}
diff --git a/platform/Windows/libsmackerdec.vcxproj b/platform/Windows/libsmackerdec.vcxproj
new file mode 100644
index 000000000..ea5001327
--- /dev/null
+++ b/platform/Windows/libsmackerdec.vcxproj
@@ -0,0 +1,207 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 15.0
+ {598F0D83-2C1B-4F7C-B04D-7FDD471C8C45}
+ libsmackerdec
+ 10.0.17134.0
+
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+ StaticLibrary
+ true
+ v142
+ Unicode
+
+
+ StaticLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ $(SolutionDir)Build\$(ProjectName)\$(Platform)\$(Configuration)\
+ $(Platform)\Build\$(ProjectName)\$(Configuration)\
+
+
+ true
+ $(SolutionDir)Build\$(ProjectName)\$(Platform)\$(Configuration)\
+ $(Platform)\Build\$(ProjectName)\$(Configuration)\
+
+
+ true
+ $(SolutionDir)Build\$(ProjectName)\$(Platform)\$(Configuration)\
+ $(Platform)\Build\$(ProjectName)\$(Configuration)\
+
+
+ false
+ $(SolutionDir)Build\$(ProjectName)\$(Platform)\$(Configuration)\
+ $(Platform)\Build\$(ProjectName)\$(Configuration)\
+
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ true
+ Speed
+ true
+ stdcpp14
+ SyncCThrow
+ None
+
+
+ Windows
+ true
+ true
+ true
+
+
+
+
+
+
+ Level3
+ Disabled
+ _DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ true
+ MultiThreadedDebugDLL
+ true
+ stdcpp14
+ SyncCThrow
+ ProgramDatabase
+ false
+
+
+ Windows
+ true
+
+
+
+
+
+
+ Level3
+ Disabled
+ _DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ true
+ MultiThreadedDebugDLL
+ false
+ true
+ stdcpp14
+ SyncCThrow
+ ProgramDatabase
+ false
+
+
+ Windows
+ true
+
+
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ true
+ Speed
+ true
+ stdcpp14
+ SyncCThrow
+ None
+
+
+ Windows
+ true
+ true
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/Windows/libsmackerdec.vcxproj.filters b/platform/Windows/libsmackerdec.vcxproj.filters
new file mode 100644
index 000000000..13241b553
--- /dev/null
+++ b/platform/Windows/libsmackerdec.vcxproj.filters
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/platform/Windows/nblood.vcxproj b/platform/Windows/nblood.vcxproj
new file mode 100644
index 000000000..45823caa8
--- /dev/null
+++ b/platform/Windows/nblood.vcxproj
@@ -0,0 +1,365 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 15.0
+ {5407CB5A-4B15-41FA-B79B-8B386A14D275}
+ nblood
+ MakeFileProj
+ v142
+ 10.0.17134.0
+ nblood
+
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Application
+ $(SolutionDir)..\..\
+ $(Platform)\Build\$(ProjectName)\$(Configuration)\
+ $(NMakeIncludeSearchPath);..\..\source\build\include;..\..\source\mact\include;..\..\source\audiolib\include;..\..\source\enet\include;..\..\platform\windows\include
+ nmake /f msvc.mak DEBUG=1 WINBITS=32 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean all DEBUG=1 WINBITS=32 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean DEBUG=1 WINBITS=32 RENDERTYPE=SDL
+ USE_OPENGL;POLYMER;SDL_USEFOLDER;SDL_TARGET=2
+ nmake /f msvc.mak WINBITS=32 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean all WINBITS=32 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean WINBITS=32 RENDERTYPE=SDL
+ USE_OPENGL;POLYMER;SDL_USEFOLDER;SDL_TARGET=2
+ nmake /f msvc.mak DEBUG=1 WINBITS=64 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean all DEBUG=1 WINBITS=64 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean DEBUG=1 WINBITS=64 RENDERTYPE=SDL
+ USE_OPENGL;POLYMER;NOASM;SDL_USEFOLDER;SDL_TARGET=2
+ nmake /f msvc.mak WINBITS=64 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean all WINBITS=64 RENDERTYPE=SDL
+ nmake /f msvc.mak veryclean WINBITS=64 RENDERTYPE=SDL
+ USE_OPENGL;POLYMER;NOASM;SDL_USEFOLDER;SDL_TARGET=2
+
+
+ false
+ false
+ false
+
+
+ false
+ false
+ false
+
+
+ false
+ false
+
+
+ false
+ false
+ false
+
+
+
+ false
+
+
+ true
+ MultiThreadedDebugDLL
+ EnableFastChecks
+ Level3
+ ProgramDatabase
+ true
+ true
+ true
+ stdcpp14
+ Disabled
+ SyncCThrow
+ false
+ _CRT_SECURE_NO_WARNINGS;WIN32;RENDERTYPESDL=1;MIXERTYPEWIN=1;SDL_USEFOLDER;SDL_TARGET=2;USE_OPENGL=1;POLYMER=1;STARTUP_WINDOW;USE_LIBVPX;HAVE_VORBIS;HAVE_XMP;%(PreprocessorDefinitions)
+
+
+
+
+ false
+ UseLinkTimeCodeGeneration
+
+
+ true
+ true
+ Speed
+ true
+ Level3
+ true
+ true
+ stdcpp14
+ SyncCThrow
+ None
+
+
+
+
+ true
+ MultiThreadedDebugDLL
+ EnableFastChecks
+ false
+ Level3
+ ProgramDatabase
+ true
+ true
+ true
+ stdcpp14
+ Disabled
+ SyncCThrow
+ false
+
+
+
+
+ UseLinkTimeCodeGeneration
+
+
+ true
+ true
+ Speed
+ true
+ Level3
+ true
+ true
+ stdcpp14
+ SyncCThrow
+ None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {0029c61b-b63d-4e61-99f2-f4e49aabfc47}
+
+
+ {dbecb851-5624-4fa8-9a9d-7169d0f12ff1}
+
+
+ {a68cc5e4-567a-44c8-94fe-c1de09aeeb40}
+
+
+ {6ac1d997-8dae-4343-8dd8-da2a1ca63212}
+
+
+ {598f0d83-2c1b-4f7c-b04d-7fdd471c8c45}
+
+
+ {32d4cf70-a3d6-4cea-81d7-64c743980276}
+
+
+ {bcde1852-e2c6-4abb-84fb-5cd431764a9a}
+
+
+
+
+ %(PreprocessorDefinitions)
+ %(PreprocessorDefinitions)
+ %(PreprocessorDefinitions)
+ %(PreprocessorDefinitions)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/Windows/nblood.vcxproj.filters b/platform/Windows/nblood.vcxproj.filters
new file mode 100644
index 000000000..f9a20c903
--- /dev/null
+++ b/platform/Windows/nblood.vcxproj.filters
@@ -0,0 +1,485 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+
+
+
+
+ Resource Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Resource Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/platform/Windows/props/build_common.props b/platform/Windows/props/build_common.props
index 649af037e..3eb400a68 100644
--- a/platform/Windows/props/build_common.props
+++ b/platform/Windows/props/build_common.props
@@ -6,7 +6,7 @@
_CRT_SECURE_NO_WARNINGS;WIN32;RENDERTYPESDL=1;MIXERTYPEWIN=1;SDL_USEFOLDER;SDL_TARGET=2;USE_OPENGL=1;STARTUP_WINDOW;USE_LIBVPX;HAVE_VORBIS;HAVE_XMP;%(PreprocessorDefinitions)
- ./include;./include/vpx/;./include/sdl2/;../../source/build/include;../../source/mact/include;../../source/audiolib/include;../../source/enet/include;../../source/glad/include;../../source/libxmp-lite/include;../../source/libxmp-lite/include/libxmp-lite
+ ./include;./include/vpx/;./include/sdl2/;../../source/build/include;../../source/mact/include;../../source/audiolib/include;../../source/enet/include;../../source/glad/include;../../source/libxmp-lite/include;../../source/libxmp-lite/include/libxmp-lite;../../source/libsmackerdec/include
4996;4244;4018;4267
/J %(AdditionalOptions)
diff --git a/platform/Windows/rednukem.vcxproj b/platform/Windows/rednukem.vcxproj
index 03f24f6ff..73e8c1908 100644
--- a/platform/Windows/rednukem.vcxproj
+++ b/platform/Windows/rednukem.vcxproj
@@ -225,7 +225,6 @@
-
diff --git a/platform/Windows/rednukem.vcxproj.filters b/platform/Windows/rednukem.vcxproj.filters
index 646966325..add4d899c 100644
--- a/platform/Windows/rednukem.vcxproj.filters
+++ b/platform/Windows/rednukem.vcxproj.filters
@@ -272,9 +272,6 @@
Resource Files
-
- Source Files
-
diff --git a/source/audiolib/include/multivoc.h b/source/audiolib/include/multivoc.h
index 2dd5c9987..c07ec70a2 100644
--- a/source/audiolib/include/multivoc.h
+++ b/source/audiolib/include/multivoc.h
@@ -67,6 +67,9 @@ enum MV_Errors
MV_InvalidFile,
};
+void DisableInterrupts(void);
+void RestoreInterrupts(void);
+
extern void (*MV_Printf)(const char *fmt, ...);
const char *MV_ErrorString(int32_t ErrorNumber);
int32_t MV_VoicePlaying(int32_t handle);
diff --git a/source/audiolib/include/music.h b/source/audiolib/include/music.h
index 18a84ead6..678b94a60 100644
--- a/source/audiolib/include/music.h
+++ b/source/audiolib/include/music.h
@@ -53,12 +53,6 @@ typedef struct
uint32_t tick;
} songposition;
-enum MIDI_Device
-{
- MIDIDEVICE_NONE = -1,
- MIDIDEVICE_MPU = 0,
- MIDIDEVICE_OPL
-};
#define MUSIC_LoopSong ( 1 == 1 )
#define MUSIC_PlayOnce ( !MUSIC_LoopSong )
diff --git a/source/audiolib/src/multivoc.cpp b/source/audiolib/src/multivoc.cpp
index 0c03f66b2..76f723087 100644
--- a/source/audiolib/src/multivoc.cpp
+++ b/source/audiolib/src/multivoc.cpp
@@ -96,13 +96,13 @@ float MV_GlobalVolume = 1.f;
static int32_t lockdepth = 0;
-static FORCE_INLINE void DisableInterrupts(void)
+void DisableInterrupts(void)
{
if (!lockdepth++)
SoundDriver_Lock();
}
-static FORCE_INLINE void RestoreInterrupts(void)
+void RestoreInterrupts(void)
{
if (!--lockdepth)
SoundDriver_Unlock();
diff --git a/source/blood/Dependencies.mak b/source/blood/Dependencies.mak
new file mode 100644
index 000000000..0794ac543
--- /dev/null
+++ b/source/blood/Dependencies.mak
@@ -0,0 +1,191 @@
+ai_h=\
+ $(blood_src)/aibat.h \
+ $(blood_src)/aibeast.h \
+ $(blood_src)/aiboneel.h \
+ $(blood_src)/aiburn.h \
+ $(blood_src)/aicaleb.h \
+ $(blood_src)/aicerber.h \
+ $(blood_src)/aicult.h \
+ $(blood_src)/aigarg.h \
+ $(blood_src)/aighost.h \
+ $(blood_src)/aigilbst.h \
+ $(blood_src)/aihand.h \
+ $(blood_src)/aihound.h \
+ $(blood_src)/aiinnoc.h \
+ $(blood_src)/aipod.h \
+ $(blood_src)/airat.h \
+ $(blood_src)/aispid.h \
+ $(blood_src)/aitchern.h \
+ $(blood_src)/aiunicult.h \
+ $(blood_src)/aizomba.h \
+ $(blood_src)/aizombf.h
+
+common_h=\
+ $(engine_inc)/compat.h \
+ $(engine_inc)/common.h \
+ $(engine_inc)/pragmas.h \
+ $(engine_inc)/build.h \
+ $(engine_inc)/baselayer.h \
+ $(engine_inc)/palette.h \
+ $(engine_inc)/polymer.h \
+ $(engine_inc)/polymost.h \
+ $(engine_inc)/texcache.h \
+ $(engine_inc)/cache1d.h \
+ $(mact_inc)/file_lib.h \
+ $(mact_inc)/keyboard.h \
+ $(mact_inc)/mouse.h \
+ $(mact_inc)/joystick.h \
+ $(mact_inc)/control.h \
+ $(audiolib_inc)/fx_man.h \
+ $(audiolib_inc)/music.h \
+ $(blood_src)/common_game.h \
+ $(blood_src)/blood.h \
+ $(blood_src)/actor.h \
+ $(blood_src)/ai.h \
+ $(blood_src)/al_midi.h \
+ $(blood_src)/asound.h \
+ $(blood_src)/callback.h \
+ $(blood_src)/choke.h \
+ $(blood_src)/config.h \
+ $(blood_src)/controls.h \
+ $(blood_src)/credits.h \
+ $(blood_src)/db.h \
+ $(blood_src)/demo.h \
+ $(blood_src)/dude.h \
+ $(blood_src)/endgame.h \
+ $(blood_src)/eventq.h \
+ $(blood_src)/fire.h \
+ $(blood_src)/function.h \
+ $(blood_src)/fx.h \
+ $(blood_src)/gamedefs.h \
+ $(blood_src)/gamemenu.h \
+ $(blood_src)/getopt.h \
+ $(blood_src)/gib.h \
+ $(blood_src)/globals.h \
+ $(blood_src)/inifile.h \
+ $(blood_src)/iob.h \
+ $(blood_src)/levels.h \
+ $(blood_src)/loadsave.h \
+ $(blood_src)/map2d.h \
+ $(blood_src)/menu.h \
+ $(blood_src)/messages.h \
+ $(blood_src)/midi.h \
+ $(blood_src)/mirrors.h \
+ $(blood_src)/misc.h \
+ $(blood_src)/mpu401.h \
+ $(blood_src)/network.h \
+ $(blood_src)/opl3.h \
+ $(blood_src)/osdcmds.h \
+ $(blood_src)/player.h \
+ $(blood_src)/pqueue.h \
+ $(blood_src)/qav.h \
+ $(blood_src)/qheap.h \
+ $(blood_src)/replace.h \
+ $(blood_src)/resource.h \
+ $(blood_src)/screen.h \
+ $(blood_src)/sectorfx.h \
+ $(blood_src)/seq.h \
+ $(blood_src)/sfx.h \
+ $(blood_src)/sound.h \
+ $(blood_src)/tile.h \
+ $(blood_src)/trig.h \
+ $(blood_src)/triggers.h \
+ $(blood_src)/view.h \
+ $(blood_src)/warp.h \
+ $(blood_src)/tile.h
+
+$(blood_obj)/blood.$o: $(blood_src)/blood.cpp $(common_h)
+$(blood_obj)/actor.$o: $(blood_src)/actor.cpp $(common_h) $(ai_h)
+$(blood_obj)/ai.$o: $(blood_src)/ai.cpp $(common_h) $(ai_h)
+$(blood_obj)/aibat.$o: $(blood_src)/aibat.cpp $(common_h) $(ai_h)
+$(blood_obj)/aibeast.$o: $(blood_src)/aibeast.cpp $(common_h) $(ai_h)
+$(blood_obj)/aiboneel.$o: $(blood_src)/aiboneel.cpp $(common_h) $(ai_h)
+$(blood_obj)/aiburn.$o: $(blood_src)/aiburn.cpp $(common_h) $(ai_h)
+$(blood_obj)/aicaleb.$o: $(blood_src)/aicaleb.cpp $(common_h) $(ai_h)
+$(blood_obj)/aicerber.$o: $(blood_src)/aicerber.cpp $(common_h) $(ai_h)
+$(blood_obj)/aicult.$o: $(blood_src)/aicult.cpp $(common_h) $(ai_h)
+$(blood_obj)/aigarg.$o: $(blood_src)/aigarg.cpp $(common_h) $(ai_h)
+$(blood_obj)/aighost.$o: $(blood_src)/aighost.cpp $(common_h) $(ai_h)
+$(blood_obj)/aigilbst.$o: $(blood_src)/aigilbst.cpp $(common_h) $(ai_h)
+$(blood_obj)/aihand.$o: $(blood_src)/aihand.cpp $(common_h) $(ai_h)
+$(blood_obj)/aihound.$o: $(blood_src)/aihound.cpp $(common_h) $(ai_h)
+$(blood_obj)/aiinnoc.$o: $(blood_src)/aiinnoc.cpp $(common_h) $(ai_h)
+$(blood_obj)/aipod.$o: $(blood_src)/aipod.cpp $(common_h) $(ai_h)
+$(blood_obj)/airat.$o: $(blood_src)/airat.cpp $(common_h) $(ai_h)
+$(blood_obj)/aispid.$o: $(blood_src)/aispid.cpp $(common_h) $(ai_h)
+$(blood_obj)/aitchern.$o: $(blood_src)/aitchern.cpp $(common_h) $(ai_h)
+$(blood_obj)/aiunicult.$o: $(blood_src)/aiunicult.cpp $(common_h) $(ai_h)
+$(blood_obj)/aizomba.$o: $(blood_src)/aizomba.cpp $(common_h) $(ai_h)
+$(blood_obj)/aizombf.$o: $(blood_src)/aizombf.cpp $(common_h) $(ai_h)
+$(blood_obj)/asound.$o: $(blood_src)/asound.cpp $(common_h)
+$(blood_obj)/callback.$o: $(blood_src)/callback.cpp $(common_h)
+$(blood_obj)/choke.$o: $(blood_src)/choke.cpp $(common_h)
+$(blood_obj)/common.$o: $(blood_src)/common.cpp $(common_h)
+$(blood_obj)/config.$o: $(blood_src)/config.cpp $(common_h)
+$(blood_obj)/controls.$o: $(blood_src)/controls.cpp $(common_h)
+$(blood_obj)/credits.$o: $(blood_src)/credits.cpp $(common_h)
+$(blood_obj)/db.$o: $(blood_src)/db.cpp $(common_h)
+$(blood_obj)/demo.$o: $(blood_src)/demo.cpp $(common_h)
+$(blood_obj)/dude.$o: $(blood_src)/dude.cpp $(common_h)
+$(blood_obj)/endgame.$o: $(blood_src)/endgame.cpp $(common_h)
+$(blood_obj)/eventq.$o: $(blood_src)/eventq.cpp $(common_h)
+$(blood_obj)/fire.$o: $(blood_src)/fire.cpp $(common_h)
+$(blood_obj)/fx.$o: $(blood_src)/fx.cpp $(common_h)
+$(blood_obj)/gamemenu.$o: $(blood_src)/gamemenu.cpp $(common_h)
+$(blood_obj)/gameutil.$o: $(blood_src)/gameutil.cpp $(common_h)
+$(blood_obj)/getopt.$o: $(blood_src)/getopt.cpp $(common_h)
+$(blood_obj)/gib.$o: $(blood_src)/gib.cpp $(common_h)
+$(blood_obj)/globals.$o: $(blood_src)/globals.cpp $(common_h)
+$(blood_obj)/inifile.$o: $(blood_src)/inifile.cpp $(common_h)
+$(blood_obj)/iob.$o: $(blood_src)/iob.cpp $(common_h)
+$(blood_obj)/levels.$o: $(blood_src)/levels.cpp $(common_h)
+$(blood_obj)/loadsave.$o: $(blood_src)/loadsave.cpp $(common_h)
+$(blood_obj)/map2d.$o: $(blood_src)/map2d.cpp $(common_h)
+$(blood_obj)/menu.$o: $(blood_src)/menu.cpp $(common_h)
+$(blood_obj)/messages.$o: $(blood_src)/messages.cpp $(common_h)
+$(blood_obj)/mirrors.$o: $(blood_src)/mirrors.cpp $(common_h)
+$(blood_obj)/misc.$o: $(blood_src)/misc.cpp $(common_h)
+$(blood_obj)/network.$o: $(blood_src)/network.cpp $(common_h)
+$(blood_obj)/osdcmd.$o: $(blood_src)/osdcmd.cpp $(common_h)
+$(blood_obj)/player.$o: $(blood_src)/player.cpp $(common_h)
+$(blood_obj)/pqueue.$o: $(blood_src)/pqueue.cpp $(common_h)
+$(blood_obj)/qav.$o: $(blood_src)/qav.cpp $(common_h)
+$(blood_obj)/qheap.$o: $(blood_src)/qheap.cpp $(common_h)
+$(blood_obj)/replace.$o: $(blood_src)/replace.cpp $(common_h)
+$(blood_obj)/resource.$o: $(blood_src)/resource.cpp $(common_h)
+$(blood_obj)/screen.$o: $(blood_src)/screen.cpp $(common_h)
+$(blood_obj)/sectorfx.$o: $(blood_src)/sectorfx.cpp $(common_h)
+$(blood_obj)/seq.$o: $(blood_src)/seq.cpp $(common_h)
+$(blood_obj)/sfx.$o: $(blood_src)/sfx.cpp $(common_h)
+$(blood_obj)/sound.$o: $(blood_src)/sound.cpp $(common_h)
+$(blood_obj)/tile.$o: $(blood_src)/tile.cpp $(common_h)
+$(blood_obj)/trig.$o: $(blood_src)/trig.cpp $(common_h)
+$(blood_obj)/triggers.$o: $(blood_src)/triggers.cpp $(common_h)
+$(blood_obj)/view.$o: $(blood_src)/view.cpp $(common_h) $(ai_h)
+$(blood_obj)/warp.$o: $(blood_src)/warp.cpp $(common_h)
+$(blood_obj)/weapon.$o: $(blood_src)/weapon.cpp $(common_h)
+$(blood_obj)/winbits.$o: $(blood_src)/winbits.cpp
+
+# misc objects
+$(blood_obj)/game_icon.$o: $(blood_rsrc)/game_icon.c $(blood_rsrc)/game_icon.ico
+
+$(blood_obj)/gameres.$o: $(blood_rsrc)/gameres.rc $(blood_src)/startwin.game.h $(blood_rsrc)/game.bmp
+$(blood_obj)/buildres.$o: $(blood_rsrc)/buildres.rc $(engine_inc)/startwin.editor.h $(blood_rsrc)/build.bmp
+$(blood_obj)/startwin.game.$o: $(blood_src)/startwin.game.cpp $(blood_h) $(engine_inc)/build.h $(engine_inc)/winlayer.h $(engine_inc)/compat.h
+$(blood_obj)/startgtk.game.$o: $(blood_src)/startgtk.game.cpp $(blood_h) $(engine_inc)/dynamicgtk.h $(engine_inc)/build.h $(engine_inc)/baselayer.h $(engine_inc)/compat.h
+
+# mact objects
+$(mact_obj)/animlib.$o: $(mact_src)/animlib.cpp $(mact_inc)/animlib.h $(engine_inc)/compat.h
+$(mact_obj)/file_lib.$o: $(mact_src)/file_lib.cpp $(mact_inc)/file_lib.h
+$(mact_obj)/control.$o: $(mact_src)/control.cpp $(mact_inc)/control.h $(mact_inc)/keyboard.h $(mact_inc)/mouse.h $(mact_inc)/joystick.h $(engine_inc)/baselayer.h
+$(mact_obj)/keyboard.$o: $(mact_src)/keyboard.cpp $(mact_inc)/keyboard.h $(engine_inc)/compat.h $(engine_inc)/baselayer.h
+$(mact_obj)/joystick.$o: $(mact_src)/joystick.cpp $(mact_inc)/joystick.h $(engine_inc)/baselayer.h
+$(mact_obj)/scriplib.$o: $(mact_src)/scriplib.cpp $(mact_inc)/scriplib.h $(mact_src)/_scrplib.h $(engine_inc)/compat.h
+
+$(blood_obj)/al_midi.$o: $(blood_src)/al_midi.cpp $(engine_inc)/compat.h $(blood_src)/al_midi.h $(blood_src)/_al_midi.h $(blood_src)/opl3.h
+$(blood_obj)/gmtimbre.$o: $(blood_src)/gmtimbre.cpp
+$(blood_obj)/opl3.$o: $(blood_src)/opl3.cpp
+$(blood_obj)/midi.$o: $(blood_src)/midi.cpp $(blood_src)/_midi.h $(blood_src)/midi.h $(blood_src)/al_midi.h $(audiolib_inc)/music.h
+$(blood_obj)/oplmidi.$o: $(blood_src)/oplmidi.cpp $(blood_src)/_oplmidi.h $(blood_src)/oplmidi.h $(blood_src)/al_midi.h $(audiolib_inc)/music.h
+$(blood_obj)/mpu401.$o: $(blood_src)/mpu401.cpp $(blood_src)/mpu401.h $(audiolib_inc)/music.h
+$(blood_obj)/music.$o: $(blood_src)/music.cpp $(blood_src)/midi.h $(blood_src)/mpu401.h $(blood_src)/al_midi.h $(audiolib_inc)/music.h
diff --git a/source/blood/gpl-2.0.txt b/source/blood/gpl-2.0.txt
new file mode 100644
index 000000000..d159169d1
--- /dev/null
+++ b/source/blood/gpl-2.0.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/source/blood/rsrc/build.bmp b/source/blood/rsrc/build.bmp
new file mode 100644
index 000000000..e101f838b
Binary files /dev/null and b/source/blood/rsrc/build.bmp differ
diff --git a/source/blood/rsrc/build_icon.c b/source/blood/rsrc/build_icon.c
new file mode 100644
index 000000000..a9b6fe43b
--- /dev/null
+++ b/source/blood/rsrc/build_icon.c
@@ -0,0 +1 @@
+#include "eduke32_icon.c"
diff --git a/source/blood/rsrc/build_icon.ico b/source/blood/rsrc/build_icon.ico
new file mode 100644
index 000000000..0bfa7fb2c
Binary files /dev/null and b/source/blood/rsrc/build_icon.ico differ
diff --git a/source/blood/rsrc/buildres.rc b/source/blood/rsrc/buildres.rc
new file mode 100644
index 000000000..18557f388
--- /dev/null
+++ b/source/blood/rsrc/buildres.rc
@@ -0,0 +1,72 @@
+#define NEED_COMMCTRL_H
+#include "../../build/include/windows_inc.h"
+#include "../../build/include/startwin.editor.h"
+
+RSRC_ICON ICON "build_icon.ico"
+RSRC_BMP BITMAP "build.bmp"
+
+WIN_STARTWIN DIALOGEX DISCARDABLE 20, 40, 260, 200
+STYLE DS_MODALFRAME | DS_CENTER | DS_SETFONT | DS_FIXEDSYS | WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_SYSMENU
+CAPTION "Startup"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ CONTROL "", WIN_STARTWIN_BITMAP, "STATIC", SS_BITMAP | SS_CENTERIMAGE | WS_CHILD | WS_VISIBLE, 0, 0, 66, 172
+ CONTROL "", WIN_STARTWIN_TABCTL, WC_TABCONTROL, WS_CLIPSIBLINGS | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 5, 250, 170
+ CONTROL "&Start", WIN_STARTWIN_START, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 180, 48, 14
+ CONTROL "&Cancel", WIN_STARTWIN_CANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 207, 180, 48, 14
+
+ CONTROL "", WIN_STARTWIN_MESSAGES, "EDIT", ES_MULTILINE | ES_READONLY | WS_CHILD | WS_VSCROLL, 0, 0, 32, 32
+END
+
+WIN_STARTWINPAGE_CONFIG DIALOGEX DISCARDABLE 20, 40, 279, 168
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+CAPTION "Dialog"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ CONTROL "&2D Video mode:", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 8, 50, 8
+ CONTROL "", IDC2DVMODE, "COMBOBOX", CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 60, 6, 80, 56
+ CONTROL "&Fullscreen", IDCFULLSCREEN, "BUTTON", BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 148, 8, 49, 10
+ CONTROL "&3D Video mode:", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 24, 50, 8
+ CONTROL "", IDC3DVMODE, "COMBOBOX", CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 60, 22, 80, 56
+ CONTROL "&Always show this window at startup", IDCALWAYSSHOW, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 118, 116, 140, 8
+END
+
+#define FILEVER 1,9,9,9
+#define PRODUCTVER 1,9,9,9
+#define STRFILEVER "2.0.0devel\0"
+#define STRPRODUCTVER "2.0.0devel\0"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION FILEVER
+ PRODUCTVERSION PRODUCTVER
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x3L
+#else
+ FILEFLAGS 0x2L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "Mapster32 for EDuke32"
+ VALUE "FileVersion", STRFILEVER
+ VALUE "InternalName", "Mapster32"
+ VALUE "LegalCopyright", "Copyright © 2018 EDuke32 Developers, 1996, 2003 3D Realms Entertainment"
+ VALUE "LegalTrademarks", "Duke Nukem® is a Registered Trademark of Gearbox Software, LLC."
+ VALUE "OriginalFilename", "mapster32.exe"
+ VALUE "ProductName", "Mapster32"
+ VALUE "ProductVersion", STRPRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+1 24 "manifest.build.xml"
diff --git a/source/blood/rsrc/eduke32_icon.c b/source/blood/rsrc/eduke32_icon.c
new file mode 100644
index 000000000..4a4da8e81
--- /dev/null
+++ b/source/blood/rsrc/eduke32_icon.c
@@ -0,0 +1,20 @@
+
+#include "sdl_inc.h"
+#include "sdlappicon.h"
+
+static Uint8 sdlappicon_pixels[] = {
+#if defined _WIN32 && SDL_MAJOR_VERSION==1
+# include "eduke32_icon_32px.c"
+#else
+# include "eduke32_icon_48px.c"
+#endif
+};
+
+struct sdlappicon sdlappicon = {
+#if defined _WIN32 && SDL_MAJOR_VERSION==1
+ 32,32,
+#else
+ 48,48,
+#endif
+ sdlappicon_pixels
+};
diff --git a/source/blood/rsrc/eduke32_icon_32px.c b/source/blood/rsrc/eduke32_icon_32px.c
new file mode 100644
index 000000000..f1dcedf2d
--- /dev/null
+++ b/source/blood/rsrc/eduke32_icon_32px.c
@@ -0,0 +1,196 @@
+/* GIMP RGBA C-Source image dump (eduke32_icon_32px.c) */
+#if 0
+static const struct {
+ unsigned int width;
+ unsigned int height;
+ unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */
+ unsigned char pixel_data[32 * 32 * 4 + 1];
+} sdlappicon = {
+ 32, 32, 4,
+#endif
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\313\035\035\033\303\034\034]\311\034\034\225\340''\301\343<<\330\345JJ"
+ "\346\345JJ\346\342\065\065\330\337\040\040\301\314\035\035\225\303\034\034]\275\032"
+ "\032\033\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\302\033\033\062\320\035\035\240\345JJ\351\360\231\231\377\366\305\305\377"
+ "\372\341\341\377\375\364\364\377\375\360\360\377\373\343\343\377\365\272"
+ "\272\377\361\236\236\377\356\213\213\377\352mm\377\342\071\071\351\320\035\035"
+ "\240\302\033\033\062\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\257\030\030\007\311"
+ "\034\034\214\343AA\371\363\253\253\377\374\357\357\377\376\375\375\377\376"
+ "\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\371\371"
+ "\377\373\346\346\377\370\323\323\377\365\270\270\377\363\254\254\377\363"
+ "\262\262\377\360\231\231\377\344EE\371\302\033\033\214\257\030\030\007\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\313\035\035\062\322\036\036\320\353rr\377\366\301\301\377\371\330\330"
+ "\377\376\371\371\377\376\375\375\377\374\355\355\377\366\305\305\377\361"
+ "\236\236\377\356\207\207\377\355\202\202\377\356\215\215\377\363\256\256"
+ "\377\370\315\315\377\370\315\315\377\370\323\323\377\370\325\325\377\372"
+ "\341\341\377\361\236\236\377\325\036\036\320\302\033\033\062\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\307\034\034/\331\037"
+ "\037\350\353rr\377\360\235\235\377\363\262\262\377\374\353\353\377\367\307"
+ "\307\377\354{{\377\345JJ\377\341\064\064\377\340++\377\332\037\037\377\314\035"
+ "\035\377\303\034\034\377\313\035\035\377\340%%\377\351ff\377\366\277\277\377\374"
+ "\357\357\377\374\351\351\377\376\375\375\377\366\277\277\377\340''\350\302"
+ "\033\033/\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\314\035\035"
+ ",\327\036\036\360\351ll\377\354{{\377\365\272\272\377\370\317\317\377\347\\"
+ "\\\377\314\035\035\377\322\036\036\377\340++\377\341\064\064\377\342\065\065\377"
+ "\341\062\062\377\336\037\037\377\313\035\035\377\302\033\033\377\275\032\032\377\302"
+ "\033\033\377\314\035\035\377\351jj\377\374\353\353\377\376\375\375\377\376\375"
+ "\375\377\370\321\321\377\340%%\360\302\033\033,\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\237\026\026\005\320\035\035\322\351ff\377\353tt\377\363\253\253\377"
+ "\366\277\277\377\342\065\065\377\322\036\036\377\303\034\034\377\332\037\037\377"
+ "\342\065\065\377\343<<\377\343<<\377\342\065\065\377\337\040\040\377\320\035\035"
+ "\377\305\034\034\377\302\033\033\377\313\035\035\377\302\033\033\377\311\034\034\377"
+ "\346OO\377\371\334\334\377\375\366\366\377\371\330\330\377\361\236\236\377"
+ "\325\036\036\322\237\026\026\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\303\034\034\213"
+ "\350ee\377\360\225\225\377\365\270\270\377\365\272\272\377\307\034\034\377"
+ "\303\034\034\377\307\034\034\377\347WW\377\347WW\377\337\040\040\377\342\065\065"
+ "\377\342\067\067\377\340--\377\336\037\037\377\313\035\035\377\305\034\034\377\302"
+ "\033\033\377\340--\377\347XX\377\337\040\040\377\322\036\036\377\340''\377\367"
+ "\312\312\377\372\341\341\377\367\310\310\377\354yy\377\302\033\033\213\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\302\033\033+\342\065\065\372\363\256\256\377\363\253\253"
+ "\377\370\321\321\377\331\037\037\377\276\033\033\377\271\032\032\377\350ee\377"
+ "\374\357\357\377\367\307\307\377\320\035\035\377\262\031\031\377\242\027\027\377"
+ "\223\025\025\377\217\024\024\377\235\026\026\377\251\030\030\377\302\033\033\377\360"
+ "\225\225\377\376\373\373\377\355\204\204\377\320\035\035\377\275\032\032\377"
+ "\341..\377\371\330\330\377\365\270\270\377\363\253\253\377\342\067\067\372"
+ "\307\034\034+\000\000\000\000\000\000\000\000\314\035\035\242\357\224\224\377\367\307\307\377"
+ "\372\341\341\377\347WW\377\322\036\036\377\303\034\034\377\350__\377\373\346"
+ "\346\377\355\204\204\377\361\244\244\377\342\071\071\377z\021\021\377w\020\020"
+ "\377w\020\020\377w\020\020\377z\021\021\377w\020\020\377\244\027\027\377\364\263\263"
+ "\377\374\351\351\377\376\373\373\377\353tt\377\275\032\032\377\313\035\035\377"
+ "\346SS\377\370\323\323\377\360\231\231\377\353xx\377\320\035\035\242\000\000\000"
+ "\000\307\034\034\026\343<<\361\367\310\310\377\372\337\337\377\366\277\277\377"
+ "\302\033\033\377\311\034\034\377\343@@\377\373\346\346\377\356\211\211\377\340"
+ "--\377\366\277\277\377\370\323\323\377\223\025\025\377|\021\021\377|\021\021\377"
+ "|\021\021\377w\020\020\377z\021\021\377\354}}\377\367\310\310\377\347WW\377\370"
+ "\323\323\377\374\357\357\377\343@@\377\322\036\036\377\302\033\033\377\364\263"
+ "\263\377\364\263\263\377\360\227\227\377\341\062\062\361\307\034\034\026\302\033"
+ "\033\\\353xx\377\367\312\312\377\373\344\344\377\351ll\377\275\032\032\377\313"
+ "\035\035\377\365\272\272\377\371\334\334\377\345HH\377\363\262\262\377\376"
+ "\373\373\377\376\373\373\377\353rr\377z\021\021\377|\021\021\377|\021\021\377z"
+ "\021\021\377\244\027\027\377\375\366\366\377\376\373\373\377\364\267\267\377"
+ "\345JJ\377\370\325\325\377\363\260\260\377\302\033\033\377\322\036\036\377\350"
+ "]]\377\370\315\315\377\361\236\236\377\351ff\377\303\034\034\\\313\035\035\227"
+ "\357\220\220\377\367\310\310\377\370\323\323\377\340))\377\302\033\033\377"
+ "\347WW\377\376\373\373\377\360\233\233\377\353vv\377\376\373\373\377\376"
+ "\373\373\377\376\373\373\377\375\362\362\377\244\027\027\377\214\023\023\377"
+ "\235\026\026\377\217\024\024\377\353rr\377\376\373\373\377\376\373\373\377\376"
+ "\373\373\377\353tt\377\353vv\377\373\346\346\377\343AA\377\311\034\034\377"
+ "\331\037\037\377\367\312\312\377\363\256\256\377\355\202\202\377\313\035\035"
+ "\227\337\040\040\303\361\236\236\377\370\323\323\377\362\245\245\377\311\034"
+ "\034\377\275\032\032\377\357\220\220\377\374\353\353\377\347ZZ\377\371\330\330"
+ "\377\376\373\373\377\376\373\373\377\376\373\373\377\376\373\373\377\372"
+ "\337\337\377\373\346\346\377\376\373\373\377\367\307\307\377\376\373\373"
+ "\377\376\373\373\377\376\373\373\377\376\373\373\377\371\330\330\377\345"
+ "HH\377\370\325\325\377\356\213\213\377\311\034\034\377\276\033\033\377\362\247"
+ "\247\377\367\307\307\377\360\225\225\377\340%%\303\342\067\067\330\363\262"
+ "\262\377\372\335\335\377\356\215\215\377\275\032\032\377\313\035\035\377\364"
+ "\265\265\377\366\277\277\377\343AA\377\376\373\373\377\376\373\373\377\376"
+ "\373\373\377\376\373\373\377\376\373\373\377\376\373\373\377\372\341\341"
+ "\377\366\305\305\377\376\373\373\377\376\373\373\377\376\373\373\377\376"
+ "\373\373\377\376\373\373\377\376\373\373\377\343AA\377\364\265\265\377\363"
+ "\262\262\377\305\034\034\377\303\034\034\377\356\213\213\377\370\323\323\377"
+ "\363\253\253\377\342\071\071\330\344GG\346\367\314\314\377\373\346\346\377"
+ "\355\202\202\377\275\032\032\377\336\037\037\377\371\330\330\377\370\321\321"
+ "\377\357\224\224\377\376\373\373\377\376\373\373\377\376\373\373\377\376"
+ "\373\373\377\376\373\373\377\366\277\277\377\217\024\024\377z\021\021\377\337"
+ "\"\"\377\375\366\366\377\376\373\373\377\376\373\373\377\376\373\373\377"
+ "\376\373\373\377\357\220\220\377\367\310\310\377\370\325\325\377\325\036\036"
+ "\377\303\034\034\377\354}}\377\372\335\335\377\364\267\267\377\343AA\346\345"
+ "JJ\346\371\332\332\377\375\360\360\377\355\206\206\377\307\034\034\377\337"
+ "\040\040\377\355\206\206\377\360\235\235\377\353vv\377\352qq\377\353rr\377"
+ "\353rr\377\354}}\377\376\373\373\377\353vv\377w\020\020\377w\020\020\377\214"
+ "\023\023\377\376\367\367\377\371\330\330\377\353rr\377\353rr\377\352qq\377"
+ "\353vv\377\357\220\220\377\353vv\377\311\034\034\377\303\034\034\377\354}}\377"
+ "\371\332\332\377\364\267\267\377\344EE\346\343AA\330\371\330\330\377\376"
+ "\367\367\377\360\225\225\377\331\037\037\377\337\040\040\377\325\036\036\377\313"
+ "\035\035\377\230\025\025\377w\020\020\377z\021\021\377p\020\020\377\217\024\024\377\373"
+ "\350\350\377\375\362\362\377\235\026\026\377|\021\021\377\346UU\377\376\373\373"
+ "\377\342\071\071\377z\021\021\377z\021\021\377w\020\020\377\217\024\024\377\302\033"
+ "\033\377\275\032\032\377\302\033\033\377\275\032\032\377\356\213\213\377\370\325"
+ "\325\377\363\254\254\377\342\071\071\330\340))\303\367\312\312\377\376\367"
+ "\367\377\364\263\263\377\337$$\377\337\040\040\377\337\"\"\377\336\037\037\377"
+ "\260\031\031\377w\020\020\377|\021\021\377z\021\021\377z\021\021\377\345JJ\377\376"
+ "\373\373\377\375\366\366\377\374\351\351\377\376\373\373\377\371\332\332"
+ "\377\235\026\026\377|\021\021\377|\021\021\377w\020\020\377\251\030\030\377\307\034"
+ "\034\377\311\034\034\377\302\033\033\377\311\034\034\377\362\247\247\377\370\323"
+ "\323\377\361\236\236\377\340%%\303\313\035\035\227\363\262\262\377\375\362"
+ "\362\377\372\335\335\377\341\064\064\377\320\035\035\377\331\037\037\377\325\036"
+ "\036\377\302\033\033\377~\021\021\377w\020\020\377|\021\021\377w\020\020\377\212\023"
+ "\023\377\362\245\245\377\376\373\373\377\376\373\373\377\376\373\373\377\337"
+ "\"\"\377z\021\021\377|\021\021\377w\020\020\377~\021\021\377\275\032\032\377\305\034"
+ "\034\377\311\034\034\377\276\033\033\377\337$$\377\370\321\321\377\366\305\305"
+ "\377\357\220\220\377\313\035\035\227\275\032\032\\\355\202\202\377\372\341\341"
+ "\377\374\351\351\377\351jj\377\275\032\032\377\314\035\035\377\302\033\033\377"
+ "\276\033\033\377\250\030\030\377w\020\020\377z\021\021\377z\021\021\377\244\027\027\377"
+ "\375\362\362\377\376\373\373\377\376\373\373\377\376\373\373\377\362\245"
+ "\245\377z\021\021\377w\020\020\377w\020\020\377\244\027\027\377\276\033\033\377\276"
+ "\033\033\377\302\033\033\377\303\034\034\377\350__\377\371\330\330\377\365\270"
+ "\270\377\353tt\377\302\033\033\\\307\034\034\026\343<<\361\366\305\305\377\371"
+ "\332\332\377\365\272\272\377\275\032\032\377\302\033\033\377\275\032\032\377\276"
+ "\033\033\377\276\033\033\377\237\026\026\377z\021\021\377\217\024\024\377\375\362\362"
+ "\377\376\373\373\377\376\373\373\377\376\373\373\377\376\373\373\377\376"
+ "\373\373\377\346OO\377z\021\021\377\237\026\026\377\276\033\033\377\275\032\032\377"
+ "\275\032\032\377\302\033\033\377\303\034\034\377\365\270\270\377\366\305\305\377"
+ "\363\253\253\377\342\067\067\361\307\034\034\026\000\000\000\000\313\035\035\242\355\206"
+ "\206\377\363\262\262\377\371\326\326\377\346QQ\377\311\034\034\377\275\032\032"
+ "\377\275\032\032\377\276\033\033\377\276\033\033\377\232\026\026\377\346OO\377\372"
+ "\341\341\377\376\373\373\377\376\373\373\377\376\373\373\377\376\373\373"
+ "\377\372\335\335\377\366\277\277\377\260\031\031\377\302\033\033\377\276\033\033"
+ "\377\276\033\033\377\275\032\032\377\320\035\035\377\345NN\377\370\325\325\377"
+ "\361\244\244\377\355\202\202\377\320\035\035\242\000\000\000\000\000\000\000\000\302\033\033"
+ "+\340--\372\361\236\236\377\362\251\251\377\371\330\330\377\337\040\040\377"
+ "\311\034\034\377\275\032\032\377\276\033\033\377\275\032\032\377\347ZZ\377\371\334"
+ "\334\377\347\\\\\377\347WW\377\351ff\377\351ff\377\350__\377\343AA\377\365"
+ "\272\272\377\370\315\315\377\307\034\034\377\275\032\032\377\275\032\032\377\313"
+ "\035\035\377\337\040\040\377\370\323\323\377\360\235\235\377\360\235\235\377"
+ "\342\067\067\372\307\034\034+\000\000\000\000\000\000\000\000\000\000\000\000\302\033\033\213\350aa\377"
+ "\362\247\247\377\367\310\310\377\366\305\305\377\322\036\036\377\311\034\034"
+ "\377\276\033\033\377\275\032\032\377\357\217\217\377\376\373\373\377\373\350"
+ "\350\377\370\317\317\377\370\315\315\377\370\315\315\377\370\315\315\377"
+ "\374\351\351\377\376\367\367\377\363\262\262\377\325\036\036\377\275\032\032"
+ "\377\313\035\035\377\322\036\036\377\367\307\307\377\367\307\307\377\360\231"
+ "\231\377\351jj\377\311\034\034\213\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\237\026\026"
+ "\005\320\035\035\322\353xx\377\360\227\227\377\366\277\277\377\367\310\310\377"
+ "\341\062\062\377\311\034\034\377\276\033\033\377\325\036\036\377\345JJ\377\357\217"
+ "\217\377\364\265\265\377\370\315\315\377\370\315\315\377\364\265\265\377"
+ "\357\217\217\377\345JJ\377\302\033\033\377\275\032\032\377\313\035\035\377\343"
+ "<<\377\367\310\310\377\366\305\305\377\360\235\235\377\355\202\202\377\327"
+ "\036\036\322\237\026\026\005\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\307\034\034"
+ ",\332\037\037\360\356\207\207\377\360\235\235\377\370\315\315\377\371\326\326"
+ "\377\347\\\\\377\302\033\033\377\275\032\032\377\302\033\033\377\275\032\032\377"
+ "\320\035\035\377\337\040\040\377\337\"\"\377\320\035\035\377\275\032\032\377\311"
+ "\034\034\377\303\034\034\377\275\032\032\377\347\\\\\377\371\334\334\377\370\315"
+ "\315\377\361\236\236\377\360\225\225\377\340%%\360\307\034\034,\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\307\034\034/\336\037\037\350\355"
+ "\202\202\377\363\262\262\377\365\274\274\377\373\344\344\377\366\277\277"
+ "\377\352oo\377\341\062\062\377\320\035\035\377\307\034\034\377\302\033\033\377\302"
+ "\033\033\377\307\034\034\377\314\035\035\377\340--\377\351ll\377\364\267\267\377"
+ "\371\334\334\377\365\270\270\377\364\267\267\377\356\215\215\377\337$$\350"
+ "\307\034\034/\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\307\034\034\062\322\036\036\320\353tt\377\366\301\301\377\367\312\312"
+ "\377\372\341\341\377\373\346\346\377\370\325\325\377\363\256\256\377\360"
+ "\231\231\377\356\207\207\377\356\207\207\377\360\235\235\377\363\262\262"
+ "\377\370\325\325\377\372\341\341\377\370\323\323\377\364\263\263\377\365"
+ "\270\270\377\354}}\377\327\036\036\320\307\034\034\062\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\257\030\030"
+ "\007\303\034\034\214\343<<\371\360\225\225\377\366\305\305\377\370\315\315\377"
+ "\370\315\315\377\371\332\332\377\373\344\344\377\375\360\360\377\375\364"
+ "\364\377\373\346\346\377\371\330\330\377\367\312\312\377\367\310\310\377"
+ "\365\272\272\377\357\220\220\377\343<<\371\307\034\034\214\257\030\030\007\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\302\033\033\062\314\035\035\240\342\071\071"
+ "\351\354yy\377\360\225\225\377\363\254\254\377\366\305\305\377\371\330\330"
+ "\377\371\330\330\377\367\307\307\377\363\254\254\377\357\224\224\377\353"
+ "xx\377\343<<\351\320\035\035\240\302\033\033\062\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\275\032\032\033\276\033\033]\311\034"
+ "\034\225\337$$\301\343<<\330\344GG\346\344GG\346\343<<\330\340''\301\313\035"
+ "\035\225\276\033\033]\275\032\032\033\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
+#if 0
+};
+#endif
diff --git a/source/blood/rsrc/eduke32_icon_48px.c b/source/blood/rsrc/eduke32_icon_48px.c
new file mode 100644
index 000000000..918b9ca48
--- /dev/null
+++ b/source/blood/rsrc/eduke32_icon_48px.c
@@ -0,0 +1,423 @@
+/* GIMP RGBA C-Source image dump (test.c) */
+#if 0
+static const struct {
+ unsigned int width;
+ unsigned int height;
+ unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */
+ unsigned char pixel_data[48 * 48 * 4 + 1];
+} sdlappicon = {
+ 48, 48, 4,
+#endif
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\037\004\004\001\325\036"
+ "\036(\325\036\036_\325\036\036\222\325\036\036\270\325\036\036\321\325\036\036\343\325"
+ "\036\036\355\325\036\036\355\325\036\036\343\325\036\036\321\325\036\036\270\325\036"
+ "\036\222\325\036\036_\325\036\036(\037\004\004\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036"
+ "\036\325\036\036s\325\036\036\304\325\036\036\365\343<<\377\350aa\377\355\202\202"
+ "\377\361\242\242\377\366\277\277\377\366\301\301\377\366\277\277\377\362"
+ "\251\251\377\355\202\202\377\350ee\377\345JJ\377\340--\377\325\036\036\365"
+ "\325\036\036\304\325\036\036s\325\036\036\036\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\334\037\037&\325\036\036\222\325\036\036\354\343"
+ "AA\377\360\227\227\377\371\334\334\377\375\364\364\377\376\371\371\377\376"
+ "\371\371\377\375\366\366\377\374\353\353\377\372\341\341\377\371\330\330"
+ "\377\370\315\315\377\365\274\274\377\364\267\267\377\364\267\267\377\363"
+ "\253\253\377\360\235\235\377\352oo\377\341\062\062\377\325\036\036\354\325\036"
+ "\036\222\334\037\037&\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\311\034\034\014\325\036\036"
+ "|\325\036\036\354\345NN\377\365\270\270\377\373\350\350\377\375\362\362\377"
+ "\375\362\362\377\375\366\366\377\375\360\360\377\375\362\362\377\375\362"
+ "\362\377\373\350\350\377\371\334\334\377\371\326\326\377\367\312\312\377"
+ "\365\270\270\377\363\262\262\377\363\254\254\377\361\244\244\377\361\242"
+ "\242\377\362\247\247\377\363\262\262\377\361\236\236\377\344GG\377\325\036"
+ "\036\354\325\036\036|\311\034\034\014\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036\064\325\036\036\305\341\062"
+ "\062\377\360\231\231\377\367\310\310\377\370\325\325\377\371\334\334\377\373"
+ "\350\350\377\374\357\357\377\375\364\364\377\375\366\366\377\375\366\366"
+ "\377\376\367\367\377\375\366\366\377\375\366\366\377\375\364\364\377\374"
+ "\353\353\377\372\341\341\377\370\323\323\377\365\270\270\377\361\236\236"
+ "\377\361\236\236\377\361\244\244\377\363\262\262\377\366\301\301\377\367"
+ "\310\310\377\363\254\254\377\342\067\067\377\325\036\036\305\325\036\036\064\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036Z\325"
+ "\036\036\356\344GG\377\361\236\236\377\363\262\262\377\365\272\272\377\367"
+ "\310\310\377\371\326\326\377\373\350\350\377\375\364\364\377\376\371\371"
+ "\377\371\330\330\377\362\251\251\377\355\206\206\377\351ll\377\347WW\377"
+ "\346OO\377\347XX\377\352oo\377\360\227\227\377\370\325\325\377\375\362\362"
+ "\377\370\325\325\377\365\270\270\377\365\274\274\377\366\301\301\377\367"
+ "\307\307\377\370\323\323\377\372\341\341\377\351ff\377\325\036\036\356\325"
+ "\036\036Z\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036q\325\036"
+ "\036\374\347XX\377\357\220\220\377\360\225\225\377\361\242\242\377\364\263"
+ "\263\377\370\325\325\377\375\360\360\377\370\323\323\377\356\213\213\377"
+ "\345NN\377\342\067\067\377\342\067\067\377\342\067\067\377\341\062\062\377\340++"
+ "\377\337$$\377\331\037\037\377\320\035\035\377\314\035\035\377\311\034\034\377\337"
+ "\040\040\377\353tt\377\370\315\315\377\374\353\353\377\371\326\326\377\370"
+ "\315\315\377\371\330\330\377\373\346\346\377\374\351\351\377\357\224\224"
+ "\377\325\036\036\374\325\036\036q\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036q\320"
+ "\035\035\377\347XX\377\355\206\206\377\355\206\206\377\356\215\215\377\361"
+ "\244\244\377\371\330\330\377\370\321\321\377\347\\\\\377\340''\377\340--"
+ "\377\342\065\065\377\342\067\067\377\342\067\067\377\342\067\067\377\342\065\065\377"
+ "\340++\377\340%%\377\327\036\036\377\320\035\035\377\314\035\035\377\303\034\034"
+ "\377\302\033\033\377\307\034\034\377\322\036\036\377\347XX\377\370\323\323\377"
+ "\374\353\353\377\373\343\343\377\373\350\350\377\373\346\346\377\374\351"
+ "\351\377\363\254\254\377\332\037\037\377\325\036\036q\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036"
+ "Z\325\036\036\374\347WW\377\354yy\377\353xx\377\354\177\177\377\363\254\254"
+ "\377\376\367\367\377\353tt\377\313\035\035\377\327\036\036\377\337\040\040\377"
+ "\340++\377\341\064\064\377\342\067\067\377\342\067\067\377\342\067\067\377\342\065"
+ "\065\377\340--\377\340%%\377\327\036\036\377\320\035\035\377\313\035\035\377\303"
+ "\034\034\377\302\033\033\377\313\035\035\377\325\036\036\377\331\037\037\377\336\037"
+ "\037\377\356\207\207\377\376\371\371\377\374\353\353\377\373\346\346\377\372"
+ "\341\341\377\371\334\334\377\361\236\236\377\325\036\036\374\325\036\036Z\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\327"
+ "\036\036\061\325\036\036\355\345NN\377\354{{\377\352oo\377\353tt\377\364\263\263"
+ "\377\373\350\350\377\343<<\377\275\032\032\377\303\034\034\377\320\035\035\377"
+ "\336\037\037\377\340''\377\341..\377\342\065\065\377\342\067\067\377\342\067\067"
+ "\377\342\065\065\377\340--\377\337$$\377\327\036\036\377\320\035\035\377\311\034"
+ "\034\377\302\033\033\377\303\034\034\377\320\035\035\377\331\037\037\377\332\037\037"
+ "\377\340''\377\340--\377\351ff\377\374\357\357\377\374\353\353\377\371\330"
+ "\330\377\370\315\315\377\367\310\310\377\354}}\377\325\036\036\355\327\036\036"
+ "\061\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\331\037\037\013"
+ "\325\036\036\310\342\071\071\377\356\213\213\377\354\177\177\377\354yy\377\364"
+ "\263\263\377\371\326\326\377\320\035\035\377\262\031\031\377\266\031\031\377\275"
+ "\032\032\377\307\034\034\377\327\036\036\377\337$$\377\340--\377\342\065\065\377"
+ "\342\067\067\377\342\067\067\377\342\065\065\377\340--\377\337$$\377\327\036\036"
+ "\377\314\035\035\377\311\034\034\377\302\033\033\377\311\034\034\377\325\036\036\377"
+ "\331\037\037\377\337\040\040\377\340--\377\341..\377\341..\377\345JJ\377\371"
+ "\334\334\377\373\343\343\377\366\277\277\377\365\270\270\377\366\277\277"
+ "\377\346QQ\377\325\036\036\310\331\037\037\013\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\325\036\036|\336\037\037\377\360\225\225\377\357\220\220\377"
+ "\356\207\207\377\363\262\262\377\372\341\341\377\303\034\034\377\257\030\030"
+ "\377\260\031\031\377\262\031\031\377\271\032\032\377\340''\377\363\254\254\377"
+ "\340--\377\340))\377\341\062\062\377\342\067\067\377\342\067\067\377\342\067\067"
+ "\377\340--\377\337$$\377\322\036\036\377\314\035\035\377\307\034\034\377\303\034"
+ "\034\377\320\035\035\377\331\037\037\377\353tt\377\345JJ\377\341..\377\341..\377"
+ "\340++\377\340''\377\342\065\065\377\373\344\344\377\370\323\323\377\364\263"
+ "\263\377\364\263\263\377\363\254\254\377\337$$\377\325\036\036|\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\322\036\036\"\325\036\036\356\354\177\177\377\361"
+ "\244\244\377\360\227\227\377\361\242\242\377\375\366\366\377\340''\377\257"
+ "\030\030\377\253\030\030\377\253\030\030\377\260\031\031\377\343<<\377\375\360\360"
+ "\377\370\323\323\377\361\236\236\377\337$$\377\331\037\037\377\275\032\032\377"
+ "\246\027\027\377\223\025\025\377\203\022\022\377\203\022\022\377\214\023\023\377\232"
+ "\026\026\377\250\030\030\377\273\032\032\377\322\036\036\377\342\071\071\377\376\367"
+ "\367\377\375\362\362\377\351jj\377\340--\377\340''\377\336\037\037\377\325"
+ "\036\036\377\343AA\377\375\366\366\377\365\272\272\377\362\251\251\377\361"
+ "\236\236\377\355\202\202\377\325\036\036\356\322\036\036\"\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\325\036\036\222\342\067\067\377\364\267\267\377\362\251\251\377"
+ "\361\244\244\377\370\325\325\377\351ll\377\271\032\032\377\264\031\031\377\262"
+ "\031\031\377\253\030\030\377\346SS\377\376\367\367\377\362\251\251\377\360\225"
+ "\225\377\375\366\366\377\313\035\035\377\177\022\022\377~\021\021\377x\021\021\377"
+ "w\020\020\377u\020\020\377u\020\020\377w\020\020\377x\021\021\377~\021\021\377\177\022"
+ "\022\377\250\030\030\377\363\253\253\377\374\357\357\377\374\357\357\377\376"
+ "\371\371\377\354yy\377\336\037\037\377\327\036\036\377\322\036\036\377\314\035\035"
+ "\377\353tt\377\371\326\326\377\360\231\231\377\360\227\227\377\360\235\235"
+ "\377\342\065\065\377\325\036\036\222\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036\036\325"
+ "\036\036\355\360\227\227\377\366\277\277\377\363\262\262\377\367\310\310\377"
+ "\370\315\315\377\303\034\034\377\276\033\033\377\275\032\032\377\266\031\031\377"
+ "\343<<\377\376\367\367\377\357\220\220\377\354{{\377\351ff\377\356\213\213"
+ "\377\360\227\227\377w\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377"
+ "u\020\020\377u\020\020\377u\020\020\377u\020\020\377w\020\020\377\253\030\030\377\371"
+ "\330\330\377\363\253\253\377\373\344\344\377\371\334\334\377\376\371\371"
+ "\377\347XX\377\322\036\036\377\314\035\035\377\303\034\034\377\302\033\033\377\370"
+ "\315\315\377\364\267\267\377\357\220\220\377\357\220\220\377\354\177\177"
+ "\377\325\036\036\355\325\036\036\036\000\000\000\000\000\000\000\000\325\036\036q\341..\377\367"
+ "\312\312\377\366\301\301\377\365\274\274\377\374\351\351\377\346OO\377\313"
+ "\035\035\377\307\034\034\377\302\033\033\377\340''\377\374\353\353\377\361\244"
+ "\244\377\353tt\377\346QQ\377\346OO\377\365\270\270\377\376\375\375\377\302"
+ "\033\033\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020"
+ "\377u\020\020\377u\020\020\377u\020\020\377\360\231\231\377\370\323\323\377\346"
+ "OO\377\357\220\220\377\366\301\301\377\370\315\315\377\374\357\357\377\341"
+ "..\377\302\033\033\377\276\033\033\377\275\032\032\377\343AA\377\372\341\341\377"
+ "\357\220\220\377\357\224\224\377\360\235\235\377\340))\377\325\036\036q\000\000"
+ "\000\000\221\024\024\003\325\036\036\305\353tt\377\366\301\301\377\365\272\272\377"
+ "\367\312\312\377\370\315\315\377\325\036\036\377\322\036\036\377\320\035\035\377"
+ "\314\035\035\377\364\263\263\377\367\307\307\377\357\220\220\377\350aa\377"
+ "\346OO\377\367\310\310\377\376\375\375\377\376\375\375\377\367\312\312\377"
+ "|\021\021\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020"
+ "\377u\020\020\377\271\032\032\377\376\375\375\377\376\375\375\377\367\310\310"
+ "\377\346OO\377\356\207\207\377\363\262\262\377\370\315\315\377\364\263\263"
+ "\377\276\033\033\377\275\032\032\377\273\032\032\377\273\032\032\377\367\310\310"
+ "\377\363\253\253\377\360\225\225\377\360\231\231\377\350ee\377\325\036\036"
+ "\305\221\024\024\003\327\036\036'\325\036\036\366\363\253\253\377\364\267\267\377"
+ "\365\270\270\377\373\343\343\377\353rr\377\327\036\036\377\327\036\036\377\327"
+ "\036\036\377\345NN\377\374\351\351\377\363\256\256\377\357\220\220\377\341"
+ "\064\064\377\365\270\270\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\343AA\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020"
+ "\020\377u\020\020\377u\020\020\377\364\267\267\377\376\375\375\377\376\375\375"
+ "\377\376\375\375\377\365\270\270\377\341\064\064\377\356\215\215\377\360\227"
+ "\227\377\373\343\343\377\343<<\377\275\032\032\377\275\032\032\377\276\033\033"
+ "\377\350ee\377\370\323\323\377\357\224\224\377\357\224\224\377\357\220\220"
+ "\377\325\036\036\366\327\036\036'\325\036\036_\341..\377\364\267\267\377\364\263"
+ "\263\377\364\263\263\377\376\371\371\377\337\040\040\377\322\036\036\377\322"
+ "\036\036\377\327\036\036\377\366\277\277\377\370\323\323\377\365\272\272\377"
+ "\351ff\377\356\207\207\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\374\353\353\377\223\025\025\377u\020\020\377"
+ "u\020\020\377u\020\020\377u\020\020\377u\020\020\377\320\035\035\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\356\207"
+ "\207\377\346SS\377\357\224\224\377\365\270\270\377\365\272\272\377\275\032"
+ "\032\377\275\032\032\377\275\032\032\377\314\035\035\377\376\367\367\377\360\231"
+ "\231\377\360\231\231\377\361\242\242\377\340++\377\325\036\036_\325\036\036\222"
+ "\344GG\377\364\267\267\377\363\262\262\377\366\277\277\377\367\307\307\377"
+ "\320\035\035\377\320\035\035\377\320\035\035\377\340--\377\376\371\371\377\365"
+ "\274\274\377\361\242\242\377\343<<\377\372\335\335\377\376\375\375\377\376"
+ "\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\354\177\177\377\241\027\027\377\346SS\377\352oo\377\340%%\377\212\023\023"
+ "\377\367\312\312\377\376\375\375\377\376\375\375\377\376\375\375\377\376"
+ "\375\375\377\376\375\375\377\372\335\335\377\343<<\377\355\202\202\377\360"
+ "\225\225\377\376\367\367\377\331\037\037\377\275\032\032\377\276\033\033\377\302"
+ "\033\033\377\366\305\305\377\364\263\263\377\361\236\236\377\363\253\253\377"
+ "\344CC\377\325\036\036\222\325\036\036\272\351ff\377\364\263\263\377\363\256"
+ "\256\377\370\323\323\377\357\220\220\377\320\035\035\377\320\035\035\377\320"
+ "\035\035\377\351ff\377\372\341\341\377\364\263\263\377\352oo\377\354yy\377"
+ "\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375"
+ "\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375"
+ "\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\354yy\377\350]]\377\360\231\231\377\371"
+ "\330\330\377\350aa\377\302\033\033\377\302\033\033\377\303\034\034\377\357\220"
+ "\220\377\367\312\312\377\361\242\242\377\362\247\247\377\351jj\377\325\036"
+ "\036\272\325\036\036\321\355\202\202\377\365\270\270\377\363\262\262\377\372"
+ "\341\341\377\352mm\377\320\035\035\377\314\035\035\377\314\035\035\377\360\227"
+ "\227\377\370\321\321\377\363\256\256\377\345JJ\377\363\256\256\377\376\375"
+ "\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375"
+ "\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375"
+ "\375\377\376\375\375\377\363\256\256\377\344EE\377\361\236\236\377\367\310"
+ "\310\377\357\224\224\377\303\034\034\377\303\034\034\377\303\034\034\377\351jj"
+ "\377\371\330\330\377\362\247\247\377\363\253\253\377\356\207\207\377\325"
+ "\036\036\321\325\036\036\343\361\242\242\377\365\270\270\377\364\263\263\377"
+ "\373\350\350\377\347WW\377\320\035\035\377\320\035\035\377\320\035\035\377\365"
+ "\272\272\377\367\307\307\377\363\262\262\377\341..\377\371\326\326\377\376"
+ "\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\376\375\375\377\376\375\375\377\376\375\375\377\363\256\256\377\244"
+ "\027\027\377u\020\020\377\276\033\033\377\370\323\323\377\376\375\375\377\376\375"
+ "\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377"
+ "\376\375\375\377\371\326\326\377\340--\377\362\247\247\377\365\274\274\377"
+ "\366\277\277\377\311\034\034\377\311\034\034\377\311\034\034\377\346QQ\377\373"
+ "\344\344\377\363\253\253\377\363\254\254\377\357\224\224\377\325\036\036\343"
+ "\325\036\036\355\364\263\263\377\366\305\305\377\366\277\277\377\374\357\357"
+ "\377\345NN\377\327\036\036\377\327\036\036\377\327\036\036\377\370\315\315\377"
+ "\367\312\312\377\367\310\310\377\345JJ\377\375\364\364\377\376\375\375\377"
+ "\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375"
+ "\375\377\376\375\375\377\376\367\367\377\223\025\025\377u\020\020\377u\020\020"
+ "\377u\020\020\377\275\032\032\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\375"
+ "\364\364\377\344GG\377\365\274\274\377\365\274\274\377\367\312\312\377\313"
+ "\035\035\377\313\035\035\377\313\035\035\377\344CC\377\374\351\351\377\363\254"
+ "\254\377\363\256\256\377\361\244\244\377\325\036\036\355\325\036\036\355\365"
+ "\270\270\377\367\312\312\377\367\310\310\377\375\360\360\377\346QQ\377\336"
+ "\037\037\377\336\037\037\377\336\037\037\377\363\254\254\377\370\321\321\377\370"
+ "\321\321\377\365\272\272\377\364\267\267\377\364\267\267\377\364\267\267"
+ "\377\364\267\267\377\364\267\267\377\364\267\267\377\372\341\341\377\376"
+ "\375\375\377\367\307\307\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377"
+ "\201\022\022\377\376\375\375\377\376\375\375\377\370\323\323\377\364\267\267"
+ "\377\364\267\267\377\364\267\267\377\364\267\267\377\364\267\267\377\365"
+ "\272\272\377\370\315\315\377\370\315\315\377\361\244\244\377\311\034\034\377"
+ "\311\034\034\377\311\034\034\377\343AA\377\373\350\350\377\363\253\253\377\363"
+ "\254\254\377\361\244\244\377\325\036\036\355\325\036\036\343\365\270\270\377"
+ "\370\323\323\377\370\315\315\377\374\357\357\377\350aa\377\337\040\040\377"
+ "\337$$\377\337$$\377\337$$\377\337$$\377\337$$\377\221\024\024\377w\020\020\377"
+ "u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377\346OO\377\376\375"
+ "\375\377\375\360\360\377\210\023\023\377u\020\020\377u\020\020\377u\020\020\377\253"
+ "\030\030\377\376\375\375\377\376\375\375\377\320\035\035\377u\020\020\377u\020\020"
+ "\377u\020\020\377u\020\020\377w\020\020\377\214\023\023\377\311\034\034\377\311\034"
+ "\034\377\311\034\034\377\311\034\034\377\311\034\034\377\311\034\034\377\346QQ\377"
+ "\373\344\344\377\363\253\253\377\363\253\253\377\360\235\235\377\325\036\036"
+ "\343\325\036\036\321\361\242\242\377\371\330\330\377\370\325\325\377\374\353"
+ "\353\377\354yy\377\337$$\377\340''\377\340''\377\340''\377\340''\377\340"
+ "%%\377\241\027\027\377x\021\021\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377"
+ "u\020\020\377\331\037\037\377\376\375\375\377\376\375\375\377\360\225\225\377"
+ "\210\023\023\377u\020\020\377\233\026\026\377\366\301\301\377\376\375\375\377\376"
+ "\375\375\377\233\026\026\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377x\021"
+ "\021\377\232\026\026\377\313\035\035\377\313\035\035\377\311\034\034\377\311\034\034"
+ "\377\311\034\034\377\311\034\034\377\351jj\377\371\334\334\377\363\253\253\377"
+ "\363\256\256\377\356\207\207\377\325\036\036\321\325\036\036\272\354\177\177"
+ "\377\371\334\334\377\371\330\330\377\373\346\346\377\360\235\235\377\340"
+ "%%\377\340''\377\340%%\377\340%%\377\337\040\040\377\337\040\040\377\262\031\031"
+ "\377~\021\021\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377~\021"
+ "\021\377\367\312\312\377\376\375\375\377\376\375\375\377\376\375\375\377\373"
+ "\344\344\377\376\375\375\377\376\375\375\377\376\375\375\377\360\231\231"
+ "\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377~\021\021\377\251"
+ "\030\030\377\311\034\034\377\311\034\034\377\311\034\034\377\311\034\034\377\311\034"
+ "\034\377\313\035\035\377\357\220\220\377\370\321\321\377\363\254\254\377\363"
+ "\262\262\377\352mm\377\325\036\036\272\325\036\036\222\347XX\377\371\334\334"
+ "\377\371\330\330\377\371\334\334\377\367\312\312\377\337$$\377\337\040\040"
+ "\377\337\040\040\377\336\037\037\377\332\037\037\377\331\037\037\377\303\034\034\377"
+ "\177\022\022\377w\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020"
+ "\020\377\235\026\026\377\367\307\307\377\376\375\375\377\376\375\375\377\376"
+ "\375\375\377\376\375\375\377\376\375\375\377\361\236\236\377\212\023\023\377"
+ "u\020\020\377u\020\020\377u\020\020\377u\020\020\377w\020\020\377\177\022\022\377\271"
+ "\032\032\377\307\034\034\377\307\034\034\377\311\034\034\377\311\034\034\377\311\034"
+ "\034\377\311\034\034\377\366\305\305\377\366\277\277\377\363\256\256\377\363"
+ "\262\262\377\345JJ\377\325\036\036\222\325\036\036_\342\065\065\377\371\330\330"
+ "\377\370\325\325\377\370\321\321\377\376\371\371\377\341..\377\336\037\037"
+ "\377\331\037\037\377\327\036\036\377\327\036\036\377\320\035\035\377\313\035\035\377"
+ "\241\027\027\377|\021\021\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020"
+ "\020\377u\020\020\377\351ll\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\376\375\375\377\376\375\375\377\340%%\377u\020\020\377u\020\020\377u\020"
+ "\020\377u\020\020\377u\020\020\377|\021\021\377\241\027\027\377\302\033\033\377\302"
+ "\033\033\377\303\034\034\377\311\034\034\377\311\034\034\377\311\034\034\377\332\037"
+ "\037\377\376\371\371\377\363\254\254\377\363\254\254\377\363\262\262\377\340"
+ "--\377\325\036\036_\327\036\036'\325\036\036\366\366\277\277\377\367\312\312\377"
+ "\367\307\307\377\373\346\346\377\353rr\377\327\036\036\377\322\036\036\377\320"
+ "\035\035\377\313\035\035\377\302\033\033\377\276\033\033\377\266\031\031\377\177\022"
+ "\022\377w\020\020\377u\020\020\377u\020\020\377u\020\020\377u\020\020\377\253\030\030"
+ "\377\376\367\367\377\376\375\375\377\376\375\375\377\376\375\375\377\376"
+ "\375\375\377\376\375\375\377\373\344\344\377\210\023\023\377u\020\020\377u\020"
+ "\020\377u\020\020\377w\020\020\377\177\022\022\377\262\031\031\377\275\032\032\377\276"
+ "\033\033\377\302\033\033\377\302\033\033\377\303\034\034\377\303\034\034\377\351ff"
+ "\377\371\334\334\377\362\251\251\377\363\253\253\377\362\247\247\377\325"
+ "\036\036\366\327\036\036'\221\024\024\003\325\036\036\305\354}}\377\367\310\310\377"
+ "\366\301\301\377\370\315\315\377\367\312\312\377\320\035\035\377\311\034\034"
+ "\377\303\034\034\377\302\033\033\377\275\032\032\377\275\032\032\377\276\033\033\377"
+ "\253\030\030\377~\021\021\377w\020\020\377u\020\020\377u\020\020\377w\020\020\377\365"
+ "\272\272\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\376\375\375\377\376\375\375\377\376\375\375\377\354\177\177\377u\020"
+ "\020\377u\020\020\377w\020\020\377~\021\021\377\253\030\030\377\271\032\032\377\271"
+ "\032\032\377\273\032\032\377\275\032\032\377\276\033\033\377\302\033\033\377\303\034"
+ "\034\377\367\310\310\377\364\267\267\377\361\244\244\377\363\253\253\377\352"
+ "oo\377\325\036\036\305\221\024\024\003\000\000\000\000\325\036\036q\340--\377\367\307\307"
+ "\377\365\270\270\377\363\262\262\377\373\346\346\377\344GG\377\302\033\033"
+ "\377\275\032\032\377\275\032\032\377\275\032\032\377\275\032\032\377\275\032\032\377"
+ "\275\032\032\377\250\030\030\377~\021\021\377w\020\020\377u\020\020\377\343AA\377\376"
+ "\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\271"
+ "\032\032\377w\020\020\377~\021\021\377\250\030\030\377\275\032\032\377\275\032\032\377"
+ "\273\032\032\377\273\032\032\377\271\032\032\377\275\032\032\377\275\032\032\377\343"
+ "AA\377\373\344\344\377\361\236\236\377\361\244\244\377\363\254\254\377\340"
+ "++\377\325\036\036q\000\000\000\000\000\000\000\000\325\036\036\036\325\036\036\355\356\215\215"
+ "\377\363\262\262\377\362\247\247\377\365\272\272\377\370\315\315\377\276"
+ "\033\033\377\275\032\032\377\275\032\032\377\275\032\032\377\275\032\032\377\275\032"
+ "\032\377\273\032\032\377\275\032\032\377\257\030\030\377\177\022\022\377\237\026\026"
+ "\377\374\353\353\377\376\375\375\377\376\375\375\377\376\375\375\377\376"
+ "\375\375\377\376\375\375\377\376\375\375\377\376\375\375\377\376\375\375"
+ "\377\373\346\346\377\370\315\315\377\206\023\023\377\260\031\031\377\276\033\033"
+ "\377\275\032\032\377\276\033\033\377\275\032\032\377\273\032\032\377\271\032\032\377"
+ "\271\032\032\377\275\032\032\377\370\315\315\377\365\270\270\377\360\227\227"
+ "\377\361\242\242\377\356\213\213\377\325\036\036\355\325\036\036\036\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\325\036\036\222\340--\377\362\247\247\377\360\227\227\377\360"
+ "\225\225\377\370\323\323\377\352mm\377\275\032\032\377\275\032\032\377\275\032"
+ "\032\377\273\032\032\377\273\032\032\377\275\032\032\377\302\033\033\377\303\034\034"
+ "\377\276\033\033\377\363\254\254\377\357\224\224\377\354yy\377\363\256\256"
+ "\377\371\326\326\377\375\362\362\377\375\362\362\377\371\326\326\377\363"
+ "\256\256\377\354yy\377\343<<\377\365\270\270\377\355\202\202\377\311\034\034"
+ "\377\303\034\034\377\276\033\033\377\275\032\032\377\275\032\032\377\275\032\032\377"
+ "\275\032\032\377\273\032\032\377\351jj\377\370\315\315\377\356\215\215\377\357"
+ "\224\224\377\361\236\236\377\343<<\377\325\036\036\222\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\322\036\036\"\325\036\036\356\353tt\377\360\227\227\377\360\225"
+ "\225\377\362\251\251\377\375\366\366\377\340--\377\275\032\032\377\273\032\032"
+ "\377\273\032\032\377\275\032\032\377\302\033\033\377\303\034\034\377\311\034\034\377"
+ "\350aa\377\373\346\346\377\361\242\242\377\353tt\377\345NN\377\341\062\062"
+ "\377\336\037\037\377\336\037\037\377\341\062\062\377\345JJ\377\352oo\377\360\231"
+ "\231\377\363\262\262\377\375\366\366\377\341\062\062\377\311\034\034\377\302"
+ "\033\033\377\275\032\032\377\275\032\032\377\275\032\032\377\275\032\032\377\340--"
+ "\377\375\366\366\377\361\236\236\377\356\213\213\377\357\220\220\377\354"
+ "}}\377\325\036\036\356\322\036\036\"\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\325\036\036|\331\037\037\377\360\227\227\377\360\231\231\377\360\227\227\377"
+ "\366\277\277\377\372\341\341\377\327\036\036\377\273\032\032\377\275\032\032\377"
+ "\276\033\033\377\302\033\033\377\307\034\034\377\332\037\037\377\373\350\350\377"
+ "\370\323\323\377\366\277\277\377\365\270\270\377\365\270\270\377\366\277"
+ "\277\377\367\310\310\377\367\312\312\377\366\277\277\377\364\267\267\377"
+ "\364\263\263\377\364\263\263\377\367\312\312\377\373\346\346\377\356\207"
+ "\207\377\311\034\034\377\303\034\034\377\302\033\033\377\275\032\032\377\275\032\032"
+ "\377\327\036\036\377\373\343\343\377\366\277\277\377\357\220\220\377\357\220"
+ "\220\377\357\224\224\377\340''\377\325\036\036|\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\331\037\037\013\325\036\036\310\342\067\067\377\361\236\236"
+ "\377\357\224\224\377\357\220\220\377\366\305\305\377\371\330\330\377\336"
+ "\037\037\377\276\033\033\377\302\033\033\377\303\034\034\377\311\034\034\377\320\035"
+ "\035\377\346QQ\377\366\277\277\377\376\367\367\377\373\344\344\377\370\325"
+ "\325\377\371\330\330\377\370\325\325\377\371\326\326\377\371\330\330\377"
+ "\370\325\325\377\373\343\343\377\375\366\366\377\365\274\274\377\345JJ\377"
+ "\303\034\034\377\307\034\034\377\311\034\034\377\307\034\034\377\302\033\033\377\336"
+ "\037\037\377\371\330\330\377\367\310\310\377\360\225\225\377\357\224\224\377"
+ "\360\225\225\377\345JJ\377\325\036\036\310\331\037\037\013\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\327\036\036\061\325\036\036\355\347XX\377"
+ "\360\225\225\377\357\220\220\377\357\220\220\377\366\301\301\377\374\351"
+ "\351\377\343AA\377\302\033\033\377\311\034\034\377\314\035\035\377\322\036\036\377"
+ "\327\036\036\377\327\036\036\377\337$$\377\350ee\377\361\236\236\377\363\253"
+ "\253\377\370\321\321\377\370\321\321\377\363\253\253\377\361\236\236\377"
+ "\350ee\377\336\037\037\377\320\035\035\377\314\035\035\377\311\034\034\377\307\034"
+ "\034\377\311\034\034\377\311\034\034\377\343AA\377\374\351\351\377\366\301\301"
+ "\377\360\225\225\377\360\225\225\377\360\231\231\377\352mm\377\325\036\036"
+ "\355\327\036\036\061\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\325\036\036Z\325\036\036\374\352mm\377\360\227\227\377\357\224"
+ "\224\377\360\227\227\377\366\277\277\377\376\371\371\377\354yy\377\313\035"
+ "\035\377\320\035\035\377\322\036\036\377\325\036\036\377\325\036\036\377\322\036\036"
+ "\377\320\035\035\377\325\036\036\377\331\037\037\377\336\037\037\377\336\037\037\377"
+ "\331\037\037\377\322\036\036\377\320\035\035\377\320\035\035\377\320\035\035\377\320"
+ "\035\035\377\313\035\035\377\307\034\034\377\307\034\034\377\354yy\377\376\371\371"
+ "\377\366\277\277\377\360\231\231\377\360\225\225\377\360\231\231\377\354"
+ "\177\177\377\325\036\036\374\325\036\036Z\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036q\322\036\036"
+ "\377\353rr\377\360\235\235\377\361\236\236\377\361\236\236\377\363\262\262"
+ "\377\371\334\334\377\370\321\321\377\345JJ\377\327\036\036\377\327\036\036\377"
+ "\322\036\036\377\322\036\036\377\320\035\035\377\327\036\036\377\331\037\037\377\336"
+ "\037\037\377\336\037\037\377\332\037\037\377\325\036\036\377\320\035\035\377\320\035"
+ "\035\377\320\035\035\377\320\035\035\377\313\035\035\377\344CC\377\370\315\315\377"
+ "\371\330\330\377\363\262\262\377\361\244\244\377\362\247\247\377\361\236"
+ "\236\377\355\202\202\377\336\037\037\377\325\036\036q\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\325\036\036q\325\036\036\374\351ff\377\361\236\236\377\362\251\251\377"
+ "\362\251\251\377\363\256\256\377\370\315\315\377\374\353\353\377\367\312"
+ "\312\377\354}}\377\341..\377\320\035\035\377\322\036\036\377\327\036\036\377\332"
+ "\037\037\377\336\037\037\377\336\037\037\377\332\037\037\377\327\036\036\377\322\036"
+ "\036\377\320\035\035\377\340++\377\354yy\377\367\312\312\377\373\350\350\377"
+ "\366\305\305\377\362\247\247\377\363\253\253\377\363\253\253\377\362\251"
+ "\251\377\353tt\377\325\036\036\374\325\036\036q\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\325\036\036Z\325\036\036\356\345JJ\377\363\254\254\377\364"
+ "\263\263\377\364\263\263\377\365\270\270\377\365\274\274\377\370\315\315"
+ "\377\372\341\341\377\375\364\364\377\370\323\323\377\361\236\236\377\354"
+ "{{\377\353tt\377\346QQ\377\346QQ\377\353tt\377\354{{\377\361\236\236\377"
+ "\370\323\323\377\375\364\364\377\371\334\334\377\366\305\305\377\363\254"
+ "\254\377\362\251\251\377\362\247\247\377\363\262\262\377\363\256\256\377"
+ "\346SS\377\325\036\036\356\325\036\036Z\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036\064\325\036\036\305\340))\377\357\220\220"
+ "\377\365\274\274\377\366\277\277\377\366\277\277\377\365\274\274\377\365"
+ "\272\272\377\364\263\263\377\366\301\301\377\370\325\325\377\373\343\343"
+ "\377\373\344\344\377\375\360\360\377\375\360\360\377\373\346\346\377\373"
+ "\343\343\377\370\325\325\377\366\301\301\377\364\267\267\377\364\267\267"
+ "\377\363\262\262\377\364\263\263\377\363\262\262\377\363\254\254\377\357"
+ "\220\220\377\341..\377\325\036\036\305\325\036\036\064\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\311\034\034\014\325"
+ "\036\036|\325\036\036\354\343<<\377\361\236\236\377\367\307\307\377\366\277\277"
+ "\377\365\272\272\377\364\267\267\377\364\267\267\377\365\272\272\377\366"
+ "\277\277\377\366\301\301\377\367\312\312\377\370\315\315\377\367\310\310"
+ "\377\366\277\277\377\365\270\270\377\364\267\267\377\364\263\263\377\364"
+ "\263\263\377\365\272\272\377\364\267\267\377\361\236\236\377\344EE\377\325"
+ "\036\036\354\325\036\036|\311\034\034\014\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\334\037"
+ "\037&\325\036\036\222\325\036\036\354\340))\377\353tt\377\363\256\256\377\366"
+ "\277\277\377\366\301\301\377\366\277\277\377\366\305\305\377\367\307\307"
+ "\377\370\315\315\377\370\321\321\377\370\315\315\377\367\307\307\377\366"
+ "\277\277\377\366\277\277\377\365\270\270\377\363\254\254\377\354{{\377\341"
+ "\064\064\377\325\036\036\354\325\036\036\222\334\037\037&\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\325\036\036\036\325\036\036s\325\036\036"
+ "\304\325\036\036\365\340++\377\345JJ\377\352mm\377\357\220\220\377\363\254"
+ "\254\377\364\267\267\377\364\267\267\377\363\256\256\377\357\220\220\377"
+ "\353rr\377\346OO\377\340--\377\325\036\036\365\325\036\036\304\325\036\036s\325"
+ "\036\036\036\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\037\004\004\001\325\036\036(\325\036\036_\325\036\036"
+ "\222\325\036\036\270\325\036\036\321\325\036\036\343\325\036\036\355\325\036\036\355"
+ "\325\036\036\343\325\036\036\321\325\036\036\270\325\036\036\222\325\036\036_\325\036"
+ "\036(\037\004\004\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
+#if 0
+};
+#endif
diff --git a/source/blood/rsrc/game.bmp b/source/blood/rsrc/game.bmp
new file mode 100644
index 000000000..fe974c0c8
Binary files /dev/null and b/source/blood/rsrc/game.bmp differ
diff --git a/source/blood/rsrc/game_icon.c b/source/blood/rsrc/game_icon.c
new file mode 100644
index 000000000..a9b6fe43b
--- /dev/null
+++ b/source/blood/rsrc/game_icon.c
@@ -0,0 +1 @@
+#include "eduke32_icon.c"
diff --git a/source/blood/rsrc/game_icon.ico b/source/blood/rsrc/game_icon.ico
new file mode 100644
index 000000000..e38c0303f
Binary files /dev/null and b/source/blood/rsrc/game_icon.ico differ
diff --git a/source/blood/rsrc/gameres.rc b/source/blood/rsrc/gameres.rc
new file mode 100644
index 000000000..abe958b9f
--- /dev/null
+++ b/source/blood/rsrc/gameres.rc
@@ -0,0 +1,80 @@
+#define NEED_COMMCTRL_H
+#include "../../build/include/windows_inc.h"
+#include "../src/startwin.game.h"
+
+RSRC_ICON ICON "game_icon.ico"
+RSRC_BMP BITMAP "game.bmp"
+
+WIN_STARTWIN DIALOGEX DISCARDABLE 20, 40, 260, 200
+STYLE DS_MODALFRAME | DS_CENTER | DS_SETFONT | DS_FIXEDSYS | WS_OVERLAPPED | WS_CAPTION | WS_VISIBLE | WS_SYSMENU
+CAPTION "Startup"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ CONTROL "", WIN_STARTWIN_BITMAP, "STATIC", SS_BITMAP | SS_CENTERIMAGE | WS_CHILD | WS_VISIBLE, 0, 0, 66, 172
+ CONTROL "", WIN_STARTWIN_TABCTL, WC_TABCONTROL, WS_CLIPSIBLINGS | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 5, 250, 170
+ CONTROL "&Start", WIN_STARTWIN_START, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 180, 48, 14
+ CONTROL "&Cancel", WIN_STARTWIN_CANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 207, 180, 48, 14
+
+ CONTROL "", WIN_STARTWIN_MESSAGES, "EDIT", ES_MULTILINE | ES_READONLY | WS_CHILD | WS_VSCROLL, 0, 0, 32, 32
+END
+WIN_STARTWINPAGE_CONFIG DIALOGEX DISCARDABLE 20, 40, 279, 168
+STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD
+CAPTION "Dialog"
+FONT 8, "MS Shell Dlg"
+BEGIN
+ CONTROL "&Video mode:", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 6, 50, 8
+ CONTROL "", IDCVMODE, "COMBOBOX", CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 60, 4, 86, 56
+ CONTROL "&Fullscreen", IDCFULLSCREEN, "BUTTON", BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 154, 6, 46, 10
+//#if defined POLYMER && POLYMER != 0
+// CONTROL "&Polymer", IDCPOLYMER, "BUTTON", BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 203, 6, 40, 10
+//#endif
+ CONTROL "Input devices:", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 20, 50, 8
+ CONTROL "", IDCINPUT, "COMBOBOX", CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 60, 19, 86, 56
+ CONTROL "&Game:", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 35, 100, 8
+ CONTROL "", IDCDATA, "LISTBOX", LBS_NOINTEGRALHEIGHT | LBS_USETABSTOPS | LBS_STANDARD | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 10, 45, 226, 43
+
+ CONTROL "Custom game content &directory:", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE, 5, 90, 160, 8
+ CONTROL "", IDCGAMEDIR, "COMBOBOX", CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP, 10, 99, 226, 156
+ CONTROL "&Enable ""autoload"" folder", IDCAUTOLOAD, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 116, 100, 8
+ CONTROL "&Always show this window at startup", IDCALWAYSSHOW, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 118, 116, 140, 8
+END
+
+#define FILEVER 1,9,9,9
+#define PRODUCTVER 1,9,9,9
+#define STRFILEVER "2.0.0devel\0"
+#define STRPRODUCTVER "2.0.0devel\0"
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION FILEVER
+ PRODUCTVERSION PRODUCTVER
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x3L
+#else
+ FILEFLAGS 0x2L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "NBlood"
+ VALUE "FileVersion", STRFILEVER
+ VALUE "InternalName", "NBlood"
+ VALUE "LegalCopyright", "Copyright © 2018 EDuke32 Developers, 1996, 2003 3D Realms Entertainment"
+ VALUE "LegalTrademarks", "Duke Nukem® is a Registered Trademark of Gearbox Software, LLC."
+ VALUE "OriginalFilename", "nblood.exe"
+ VALUE "ProductName", "NBlood"
+ VALUE "ProductVersion", STRPRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+1 24 "manifest.game.xml"
diff --git a/source/blood/rsrc/manifest.build.xml b/source/blood/rsrc/manifest.build.xml
new file mode 100644
index 000000000..9abe41969
--- /dev/null
+++ b/source/blood/rsrc/manifest.build.xml
@@ -0,0 +1,42 @@
+
+
+
+
+ true
+ PerMonitorV2
+
+
+
+ Mapster32 for EDuke32
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/blood/rsrc/manifest.game.xml b/source/blood/rsrc/manifest.game.xml
new file mode 100644
index 000000000..d447255e2
--- /dev/null
+++ b/source/blood/rsrc/manifest.game.xml
@@ -0,0 +1,42 @@
+
+
+
+
+ true
+ PerMonitorV2
+
+
+
+ NBlood
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/blood/rsrc/source/EDuke32_logo_21_large.psd b/source/blood/rsrc/source/EDuke32_logo_21_large.psd
new file mode 100644
index 000000000..5aeecef60
Binary files /dev/null and b/source/blood/rsrc/source/EDuke32_logo_21_large.psd differ
diff --git a/source/blood/rsrc/source/EDuke32_logo_21_large_blue.psd b/source/blood/rsrc/source/EDuke32_logo_21_large_blue.psd
new file mode 100644
index 000000000..ff32e9335
Binary files /dev/null and b/source/blood/rsrc/source/EDuke32_logo_21_large_blue.psd differ
diff --git a/source/blood/rsrc/source/EDuke32_logo_21_large_opaque.psd b/source/blood/rsrc/source/EDuke32_logo_21_large_opaque.psd
new file mode 100644
index 000000000..be7b6cbc1
Binary files /dev/null and b/source/blood/rsrc/source/EDuke32_logo_21_large_opaque.psd differ
diff --git a/source/blood/rsrc/source/game2.psd b/source/blood/rsrc/source/game2.psd
new file mode 100644
index 000000000..3b7c1fbf2
Binary files /dev/null and b/source/blood/rsrc/source/game2.psd differ
diff --git a/source/blood/rsrc/source/game3.psd b/source/blood/rsrc/source/game3.psd
new file mode 100644
index 000000000..dc6eaefe9
Binary files /dev/null and b/source/blood/rsrc/source/game3.psd differ
diff --git a/source/blood/rsrc/source/wii-hbc-icon.xcf b/source/blood/rsrc/source/wii-hbc-icon.xcf
new file mode 100644
index 000000000..7dc3df63b
Binary files /dev/null and b/source/blood/rsrc/source/wii-hbc-icon.xcf differ
diff --git a/source/blood/src/_functio.h b/source/blood/src/_functio.h
new file mode 100644
index 000000000..1bc7d564f
--- /dev/null
+++ b/source/blood/src/_functio.h
@@ -0,0 +1,376 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+// _functio.h
+
+// file created by makehead.exe
+// these headers contain default key assignments, as well as
+// default button assignments and game function names
+// axis defaults are also included
+
+#include "_control.h"
+#include "control.h"
+
+#ifndef function_private_h_
+#define function_private_h_
+#ifdef __cplusplus
+extern "C" {
+#endif
+// KEEPINSYNC lunatic/con_lang.lua
+char gamefunctions[NUMGAMEFUNCTIONS][MAXGAMEFUNCLEN] =
+ {
+ "Move_Forward",
+ "Move_Backward",
+ "Turn_Left",
+ "Turn_Right",
+ "Turn_Around",
+ "Strafe",
+ "Strafe_Left",
+ "Strafe_Right",
+ "Jump",
+ "Crouch",
+ "Run",
+ "AutoRun",
+ "Open",
+ "Weapon_Fire",
+ "Weapon_Special_Fire",
+ "Aim_Up",
+ "Aim_Down",
+ "Aim_Center",
+ "Look_Up",
+ "Look_Down",
+ "Tilt_Left",
+ "Tilt_Right",
+ "Weapon_1",
+ "Weapon_2",
+ "Weapon_3",
+ "Weapon_4",
+ "Weapon_5",
+ "Weapon_6",
+ "Weapon_7",
+ "Weapon_8",
+ "Weapon_9",
+ "Weapon_10",
+ "Inventory_Use",
+ "Inventory_Left",
+ "Inventory_Right",
+ "Map_Toggle",
+ "Map_Follow_Mode",
+ "Shrink_Screen",
+ "Enlarge_Screen",
+ "Send_Message",
+ "See_Coop_View",
+ "See_Chase_View",
+ "Mouse_Aiming",
+ "Toggle_Crosshair",
+ "Next_Weapon",
+ "Previous_Weapon",
+ "Holster_Weapon",
+ "Show_Opponents_Weapon",
+ "BeastVision",
+ "CrystalBall",
+ "JumpBoots",
+ "MedKit",
+ "ProximityBombs",
+ "RemoteBombs",
+ "Show_Console",
+ };
+
+#ifdef __SETUP__
+
+const char keydefaults[NUMGAMEFUNCTIONS*2][MAXGAMEFUNCLEN] =
+ {
+ "W", "Kpad8",
+ "S", "Kpad2",
+ "Left", "Kpad4",
+ "Right", "KPad6",
+ "BakSpc", "",
+ "LAlt", "RAlt",
+ "A", "",
+ "D", "",
+ "Space", "/",
+ "LCtrl", "",
+ "LShift", "RShift",
+ "CapLck", "",
+ "E", "",
+ "RCtrl", "",
+ "X", "",
+ "Home", "KPad7",
+ "End", "Kpad1",
+ "KPad5", "",
+ "PgUp", "Kpad9",
+ "PgDn", "Kpad3",
+ "Insert", "Kpad0",
+ "Delete", "Kpad.",
+ "1", "",
+ "2", "",
+ "3", "",
+ "4", "",
+ "5", "",
+ "6", "",
+ "7", "",
+ "8", "",
+ "9", "",
+ "0", "",
+ "Enter", "KpdEnt",
+ "[", "",
+ "]", "",
+ "Tab", "",
+ "F", "",
+ "-", "Kpad-",
+ "=", "Kpad+",
+ "T", "",
+ "K", "",
+ "F7", "",
+ "U", "",
+ "I", "",
+ "'", "",
+ ";", "",
+ "ScrLck", "",
+ "Y", "",
+ "B", "",
+ "C", "",
+ "J", "",
+ "M", "",
+ "P", "",
+ "R", "",
+ "`", "",
+ };
+
+const char oldkeydefaults[NUMGAMEFUNCTIONS*2][MAXGAMEFUNCLEN] =
+ {
+ "Up", "Kpad8",
+ "Down", "Kpad2",
+ "Left", "Kpad4",
+ "Right", "KPad6",
+ "BakSpc", "",
+ "LAlt", "RAlt",
+ ",", "",
+ ".", "",
+ "A", "/",
+ "Z", "",
+ "LShift", "RShift",
+ "CapLck", "",
+ "Space", "",
+ "LCtrl", "RCtrl",
+ "X", "",
+ "Home", "KPad7",
+ "End", "Kpad1",
+ "KPad5", "",
+ "PgUp", "Kpad9",
+ "PgDn", "Kpad3",
+ "Insert", "Kpad0",
+ "Delete", "Kpad.",
+ "1", "",
+ "2", "",
+ "3", "",
+ "4", "",
+ "5", "",
+ "6", "",
+ "7", "",
+ "8", "",
+ "9", "",
+ "0", "",
+ "Enter", "KpdEnt",
+ "[", "",
+ "]", "",
+ "Tab", "",
+ "F", "",
+ "-", "Kpad-",
+ "=", "Kpad+",
+ "T", "",
+ "K", "",
+ "F7", "",
+ "U", "",
+ "I", "",
+ "'", "",
+ ";", "",
+ "ScrLck", "",
+ "W", "",
+ "B", "",
+ "C", "",
+ "J", "",
+ "M", "",
+ "P", "",
+ "R", "",
+ "`", "",
+ };
+
+static const char * mousedefaults[MAXMOUSEBUTTONS] =
+ {
+ "Weapon_Fire",
+ "Weapon_Special_Fire",
+ "",
+ "",
+ "Previous_Weapon",
+ "Next_Weapon",
+ };
+
+
+static const char * mouseclickeddefaults[MAXMOUSEBUTTONS] =
+ {
+ };
+
+
+static const char * mouseanalogdefaults[MAXMOUSEAXES] =
+ {
+ "analog_turning",
+ "analog_moving",
+ };
+
+
+static const char * mousedigitaldefaults[MAXMOUSEDIGITAL] =
+ {
+ };
+
+#if defined(GEKKO)
+static const char * joystickdefaults[MAXJOYBUTTONSANDHATS] =
+ {
+ "Open", // A
+ "Fire", // B
+ "Run", // 1
+ "Map", // 2
+ "Previous_Weapon", // -
+ "Next_Weapon", // +
+ "", // Home
+ "Jump", // Z
+ "Crouch", // C
+ "Map", // X
+ "Run", // Y
+ "Jump", // L
+ "Quick_Kick", // R
+ "Crouch", // ZL
+ "Fire", // ZR
+ "Quick_Kick", // D-Pad Up
+ "Inventory_Right", // D-Pad Right
+ "Inventory", // D-Pad Down
+ "Inventory_Left", // D-Pad Left
+ };
+
+
+static const char * joystickclickeddefaults[MAXJOYBUTTONSANDHATS] =
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "Inventory",
+ };
+
+
+static const char * joystickanalogdefaults[MAXJOYAXES] =
+ {
+ "analog_strafing",
+ "analog_moving",
+ "analog_turning",
+ "analog_lookingupanddown",
+ };
+
+
+static const char * joystickdigitaldefaults[MAXJOYDIGITAL] =
+ {
+ };
+#else
+static const char * joystickdefaults[MAXJOYBUTTONSANDHATS] =
+ {
+ "Fire",
+ "Strafe",
+ "Run",
+ "Open",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "Aim_Down",
+ "Look_Right",
+ "Aim_Up",
+ "Look_Left",
+ };
+
+
+static const char * joystickclickeddefaults[MAXJOYBUTTONSANDHATS] =
+ {
+ "",
+ "Inventory",
+ "Jump",
+ "Crouch",
+ };
+
+
+static const char * joystickanalogdefaults[MAXJOYAXES] =
+ {
+ "analog_turning",
+ "analog_moving",
+ "analog_strafing",
+ };
+
+
+static const char * joystickdigitaldefaults[MAXJOYDIGITAL] =
+ {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "Run",
+ };
+#endif
+
+#endif
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/source/blood/src/_midi.h b/source/blood/src/_midi.h
new file mode 100644
index 000000000..f522fafca
--- /dev/null
+++ b/source/blood/src/_midi.h
@@ -0,0 +1,149 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#ifndef ___MIDI_H
+#define ___MIDI_H
+#include "compat.h"
+
+#define RELATIVE_BEAT( measure, beat, tick ) \
+ ( ( tick ) + ( ( beat ) << 9 ) + ( ( measure ) << 16 ) )
+
+//Bobby Prince thinks this may be 100
+//#define GENMIDI_DefaultVolume 100
+#define GENMIDI_DefaultVolume 90
+
+#define MAX_FORMAT 1
+
+#define NUM_MIDI_CHANNELS 16
+
+#define TIME_PRECISION 16
+
+#define MIDI_HEADER_SIGNATURE 0x6468544d // "MThd"
+#define MIDI_TRACK_SIGNATURE 0x6b72544d // "MTrk"
+
+#define MIDI_VOLUME 7
+#define MIDI_PAN 10
+#define MIDI_DETUNE 94
+#define MIDI_RHYTHM_CHANNEL 9
+#define MIDI_RPN_MSB 100
+#define MIDI_RPN_LSB 101
+#define MIDI_DATAENTRY_MSB 6
+#define MIDI_DATAENTRY_LSB 38
+#define MIDI_PITCHBEND_MSB 0
+#define MIDI_PITCHBEND_LSB 0
+#define MIDI_RUNNING_STATUS 0x80
+#define MIDI_NOTE_OFF 0x8
+#define MIDI_NOTE_ON 0x9
+#define MIDI_POLY_AFTER_TCH 0xA
+#define MIDI_CONTROL_CHANGE 0xB
+#define MIDI_PROGRAM_CHANGE 0xC
+#define MIDI_AFTER_TOUCH 0xD
+#define MIDI_PITCH_BEND 0xE
+#define MIDI_SPECIAL 0xF
+#define MIDI_SYSEX 0xF0
+#define MIDI_SYSEX_CONTINUE 0xF7
+#define MIDI_META_EVENT 0xFF
+#define MIDI_END_OF_TRACK 0x2F
+#define MIDI_TEMPO_CHANGE 0x51
+#define MIDI_TIME_SIGNATURE 0x58
+#define MIDI_RESET_ALL_CONTROLLERS 0x79
+#define MIDI_ALL_NOTES_OFF 0x7b
+#define MIDI_MONO_MODE_ON 0x7E
+#define MIDI_SYSTEM_RESET 0xFF
+
+#define GET_NEXT_EVENT( track, data ) do { \
+ ( data ) = *( track )->pos; \
+ ( track )->pos += 1; \
+} while (0)
+
+#define GET_MIDI_CHANNEL( event ) ( ( event ) & 0xf )
+#define GET_MIDI_COMMAND( event ) ( ( event ) >> 4 )
+
+#define EMIDI_INFINITE -1
+#define EMIDI_END_LOOP_VALUE 127
+#define EMIDI_ALL_CARDS 127
+#define EMIDI_INCLUDE_TRACK 110
+#define EMIDI_EXCLUDE_TRACK 111
+#define EMIDI_PROGRAM_CHANGE 112
+#define EMIDI_VOLUME_CHANGE 113
+#define EMIDI_CONTEXT_START 114
+#define EMIDI_CONTEXT_END 115
+#define EMIDI_LOOP_START 116
+#define EMIDI_LOOP_END 117
+#define EMIDI_SONG_LOOP_START 118
+#define EMIDI_SONG_LOOP_END 119
+
+#define EMIDI_GeneralMIDI 0
+#define EMIDI_SoundBlaster 4
+
+#define EMIDI_AffectsCurrentCard(c, type) (((c) == EMIDI_ALL_CARDS) || ((c) == (type)))
+#define EMIDI_NUM_CONTEXTS 7
+
+typedef struct
+{
+ char *pos;
+ char *loopstart;
+ int16_t loopcount;
+ int16_t RunningStatus;
+ unsigned time;
+ int32_t FPSecondsPerTick;
+ int16_t tick;
+ int16_t beat;
+ int16_t measure;
+ int16_t BeatsPerMeasure;
+ int16_t TicksPerBeat;
+ int16_t TimeBase;
+ int32_t delay;
+ int16_t active;
+} songcontext;
+
+typedef struct
+{
+ char *start;
+ char *pos;
+
+ int32_t delay;
+ int16_t active;
+ int16_t RunningStatus;
+
+ int16_t currentcontext;
+ songcontext context[EMIDI_NUM_CONTEXTS];
+
+ char EMIDI_IncludeTrack;
+ char EMIDI_ProgramChange;
+ char EMIDI_VolumeChange;
+} track;
+
+static int32_t _MIDI_ReadNumber(void *from, size_t size);
+static int32_t _MIDI_ReadDelta(track *ptr);
+static void _MIDI_ResetTracks(void);
+static void _MIDI_AdvanceTick(void);
+static void _MIDI_MetaEvent(track *Track);
+static void _MIDI_SysEx(track *Track);
+static int32_t _MIDI_InterpretControllerInfo(track *Track, int32_t TimeSet, int32_t channel, int32_t c1, int32_t c2);
+static int32_t _MIDI_SendControlChange(int32_t channel, int32_t c1, int32_t c2);
+static void _MIDI_SetChannelVolume(int32_t channel, int32_t volume);
+static void _MIDI_SendChannelVolumes(void);
+static void _MIDI_InitEMIDI(void);
+
+#endif
diff --git a/source/blood/src/actor.cpp b/source/blood/src/actor.cpp
new file mode 100644
index 000000000..888d08a39
--- /dev/null
+++ b/source/blood/src/actor.cpp
@@ -0,0 +1,7247 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include
+#include
+
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aibat.h"
+#include "aibeast.h"
+#include "aiboneel.h"
+#include "aiburn.h"
+#include "aicaleb.h"
+#include "aicerber.h"
+#include "aicult.h"
+#include "aigarg.h"
+#include "aighost.h"
+#include "aigilbst.h"
+#include "aihand.h"
+#include "aihound.h"
+#include "aiinnoc.h"
+#include "aipod.h"
+#include "airat.h"
+#include "aispid.h"
+#include "aitchern.h"
+#include "aizomba.h"
+#include "aizombf.h"
+#include "aiunicult.h"
+#include "blood.h"
+#include "callback.h"
+#include "config.h"
+#include "db.h"
+#include "endgame.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gameutil.h"
+#include "gib.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "tile.h"
+#include "trig.h"
+#include "triggers.h"
+#include "view.h"
+#include "warp.h"
+#include "weapon.h"
+
+VECTORDATA gVectorData[] = {
+ // Tine
+ {
+ DAMAGE_TYPE_2,
+ 17,
+ 174762,
+ 1152,
+ 10240,
+ 0,
+ 1,
+ 20480,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_43, FX_6, FX_NONE, 502,
+ FX_43, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_7, 502,
+ FX_43, FX_6, FX_7, 502,
+ FX_NONE, FX_NONE, FX_NONE, 503,
+ FX_43, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 503,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 1207, 1207,
+ },
+ // Shell
+ {
+ DAMAGE_TYPE_2,
+ 4,
+ 65536,
+ 0,
+ 8192,
+ 0,
+ 1,
+ 12288,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_5, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_43, FX_6, FX_NONE, -1,
+ FX_43, FX_0, FX_NONE, -1,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_43, FX_6, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 1001, 1001,
+ },
+ // Bullet
+ {
+ DAMAGE_TYPE_2,
+ 7,
+ 21845,
+ 0,
+ 32768,
+ 0,
+ 1,
+ 12288,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_5, FX_7, 510,
+ FX_NONE, FX_5, FX_7, 511,
+ FX_43, FX_6, FX_NONE, 512,
+ FX_43, FX_0, FX_NONE, 513,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_7, 512,
+ FX_43, FX_6, FX_7, 512,
+ FX_NONE, FX_NONE, FX_NONE, 513,
+ FX_43, FX_NONE, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 4001, 4002,
+ },
+ // Tommy AP
+ {
+ DAMAGE_TYPE_2,
+ 20,
+ 65536,
+ 0,
+ 16384,
+ 0,
+ 1,
+ 20480,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_5, FX_7, 510,
+ FX_NONE, FX_5, FX_7, 511,
+ FX_43, FX_6, FX_NONE, 512,
+ FX_43, FX_0, FX_NONE, 513,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_7, 512,
+ FX_43, FX_6, FX_7, 512,
+ FX_NONE, FX_NONE, FX_NONE, 513,
+ FX_43, FX_NONE, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 431, 431
+ },
+ // Shell AP
+ {
+ DAMAGE_TYPE_2,
+ 6,
+ 87381,
+ 0,
+ 12288,
+ 0,
+ 1,
+ 6144,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_5, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_43, FX_6, FX_NONE, -1,
+ FX_43, FX_0, FX_NONE, -1,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_43, FX_6, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 1002, 1002,
+ },
+ // Tommy regular
+ {
+ DAMAGE_TYPE_2,
+ 12,
+ 65536,
+ 0,
+ 16384,
+ 0,
+ 1,
+ 12288,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_43, FX_5, FX_7, 510,
+ FX_NONE, FX_5, FX_7, 511,
+ FX_43, FX_6, FX_NONE, 512,
+ FX_43, FX_0, FX_NONE, 513,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_7, 512,
+ FX_43, FX_6, FX_7, 512,
+ FX_NONE, FX_NONE, FX_NONE, 513,
+ FX_43, FX_NONE, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_6, FX_NONE, 513,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 359, 359,
+ },
+ // Bat bite
+ {
+ DAMAGE_TYPE_2,
+ 4,
+ 0,
+ 921,
+ 0,
+ 0,
+ 1,
+ 4096,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 521, 521,
+ },
+ // Eel bite
+ {
+ DAMAGE_TYPE_2,
+ 12,
+ 0,
+ 1177,
+ 0,
+ 0,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 513, 513
+ },
+ // Gill bite
+ {
+ DAMAGE_TYPE_2,
+ 9,
+ 0,
+ 1177,
+ 0,
+ 0,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 499, 499
+ },
+ // Beast slash
+ {
+ DAMAGE_TYPE_3,
+ 50,
+ 43690,
+ 1024,
+ 8192,
+ 0,
+ 4,
+ 32768,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 9012, 9014
+ },
+ // Axe
+ {
+ DAMAGE_TYPE_2,
+ 18,
+ 436906,
+ 1024,
+ 16384,
+ 0,
+ 2,
+ 20480,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 1101, 1101
+ },
+ // Cleaver
+ {
+ DAMAGE_TYPE_2,
+ 9,
+ 218453,
+ 1024,
+ 0,
+ 0,
+ 1,
+ 24576,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 1207, 1207
+ },
+ // Phantasm slash
+ {
+ DAMAGE_TYPE_2,
+ 20,
+ 436906,
+ 1024,
+ 16384,
+ 0,
+ 3,
+ 24576,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 499, 495
+ },
+ // Gargoyle Slash
+ {
+ DAMAGE_TYPE_2,
+ 16,
+ 218453,
+ 1024,
+ 8192,
+ 0,
+ 4,
+ 20480,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ 495, 496
+ },
+ // Cerberus bite
+ {
+ DAMAGE_TYPE_2,
+ 19,
+ 218453,
+ 614,
+ 8192,
+ 0,
+ 2,
+ 24576,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 9013, 499
+ },
+ // Hound bite
+ {
+ DAMAGE_TYPE_2,
+ 10,
+ 218453,
+ 614,
+ 8192,
+ 0,
+ 2,
+ 24576,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 1307, 1308
+ },
+ // Rat bite
+ {
+ DAMAGE_TYPE_2,
+ 4,
+ 0,
+ 921,
+ 0,
+ 0,
+ 1,
+ 24576,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 499, 499
+ },
+ // Spider bite
+ {
+ DAMAGE_TYPE_2,
+ 8,
+ 0,
+ 614,
+ 0,
+ 0,
+ 1,
+ 24576,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 499, 499
+ },
+ // Unk
+ {
+ DAMAGE_TYPE_2,
+ 9,
+ 0,
+ 512,
+ 0,
+ 0,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_5, FX_NONE, 500,
+ FX_NONE, FX_5, FX_NONE, 501,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_0, FX_NONE, 503,
+ FX_NONE, FX_4, FX_NONE, -1,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_6, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 502,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 499, 499
+ },
+
+ {
+ (DAMAGE_TYPE)-1,
+ 0,
+ 0,
+ 2560,
+ 0,
+ 0,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_34, FX_35, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 499, 499
+ },
+ // Tchernobog burn vector
+ {
+ DAMAGE_TYPE_1,
+ 2,
+ 0,
+ 0,
+ 0,
+ 15,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 351, 351
+ },
+ // Vodoo 1.0 vector
+ {
+ DAMAGE_TYPE_5,
+ 25,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ 0,0
+ },
+ // 22 kVectorGenDudePunch
+ {
+ DAMAGE_TYPE_0,
+ 37,
+ 874762,
+ 620,
+ 0,
+ 0,
+ 0,
+ 0,
+ FX_NONE, FX_NONE, FX_NONE, -1,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ FX_NONE, FX_NONE, FX_NONE, 357,
+ 357, 499
+ },
+};
+
+ITEMDATA gItemData[] = {
+ {
+ 0,
+ 2552,
+ (char)-8,
+ 0,
+ 32,
+ 32,
+ -1,
+ },
+ {
+ 0,
+ 2553,
+ (char)-8,
+ 0,
+ 32,
+ 32,
+ -1,
+ },
+ {
+ 0,
+ 2554,
+ (char)-8,
+ 0,
+ 32,
+ 32,
+ -1,
+ },
+ {
+ 0,
+ 2555,
+ (char)-8,
+ 0,
+ 32,
+ 32,
+ -1,
+ },
+ {
+ 0,
+ 2556,
+ (char)-8,
+ 0,
+ 32,
+ 32,
+ -1,
+ },
+ {
+ 0,
+ 2557,
+ (char)-8,
+ 0,
+ 32,
+ 32,
+ -1,
+ },
+ {
+ 0,
+ -1,
+ (char)-8,
+ 0,
+ 255,
+ 255,
+ -1,
+ },
+ {
+ 0,
+ 519,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 0,
+ },
+ {
+ 0,
+ 822,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 2169,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 2433,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 517,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 783,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 896,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 825,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 827,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ 4,
+ },
+ {
+ 0,
+ 828,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 829,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 830,
+ (char)-8,
+ 0,
+ 80,
+ 64,
+ 1,
+ },
+ {
+ 0,
+ 831,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 863,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 760,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ 2,
+ },
+ {
+ 0,
+ 836,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 851,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 2428,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 839,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ 3,
+ },
+ {
+ 0,
+ 768,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 840,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ -1,
+ },
+ {
+ 0,
+ 841,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ -1,
+ },
+ {
+ 0,
+ 842,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ -1,
+ },
+ {
+ 0,
+ 843,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ -1,
+ },
+ {
+ 0,
+ 683,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 521,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 604,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 520,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 803,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 518,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 522,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 523,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ -1,
+ },
+ {
+ 0,
+ 837,
+ (char)-8,
+ 0,
+ 80,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 2628,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 2586,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 2578,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 2602,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 2594,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 753,
+ (char)-8,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 753,
+ (char)-8,
+ 7,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 3558,
+ (char)-128,
+ 0,
+ 64,
+ 64,
+ -1,
+ },
+ {
+ 0,
+ 3558,
+ (char)-128,
+ 7,
+ 64,
+ 64,
+ -1,
+ }
+};
+
+AMMOITEMDATA gAmmoItemData[] = {
+ {
+ 0,
+ 618,
+ (char)-8,
+ 0,
+ 40,
+ 40,
+ 480,
+ 6,
+ 7
+ },
+ {
+ 0,
+ 589,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 1,
+ 5,
+ 6
+ },
+ {
+ 0,
+ 589,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 1,
+ 5,
+ 6
+ },
+ {
+ 0,
+ 809,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 5,
+ 5,
+ 6
+ },
+ {
+ 0,
+ 811,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 1,
+ 10,
+ 11
+ },
+ {
+ 0,
+ 810,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 1,
+ 11,
+ 12
+ },
+ {
+ 0,
+ 820,
+ (char)-8,
+ 0,
+ 24,
+ 24,
+ 10,
+ 8,
+ 0
+ },
+ {
+ 0,
+ 619,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 4,
+ 2,
+ 0
+ },
+ {
+ 0,
+ 812,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 15,
+ 2,
+ 0
+ },
+ {
+ 0,
+ 813,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 15,
+ 3,
+ 0
+ },
+ {
+ 0,
+ 525,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 100,
+ 9,
+ 10
+ },
+ {
+ 0,
+ 814,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 15,
+ 255,
+ 0
+ },
+ {
+ 0,
+ 817,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 100,
+ 3,
+ 0
+ },
+ {
+ 0,
+ 548,
+ (char)-8,
+ 0,
+ 24,
+ 24,
+ 32,
+ 7,
+ 0
+ },
+ {
+ 0,
+ 0,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 6,
+ 255,
+ 0
+ },
+ {
+ 0,
+ 0,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 6,
+ 255,
+ 0
+ },
+ {
+ 0,
+ 816,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 8,
+ 1,
+ 0
+ },
+ {
+ 0,
+ 818,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 8,
+ 255,
+ 0
+ },
+ {
+ 0,
+ 819,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 8,
+ 255,
+ 0
+ },
+ {
+ 0,
+ 801,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 6,
+ 4,
+ 0
+ },
+ {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ },
+};
+
+WEAPONITEMDATA gWeaponItemData[] = {
+ {
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1,
+ 0
+ },
+ {
+ 0,
+ 559,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 3,
+ 2,
+ 8
+ },
+ {
+ 0,
+ 558,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 4,
+ 3,
+ 50
+ },
+ {
+ 0,
+ 524,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 2,
+ 1,
+ 9
+ },
+ {
+ 0,
+ 525,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 10,
+ 9,
+ 100
+ },
+ {
+ 0,
+ 539,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 8,
+ 7,
+ 64
+ },
+ {
+ 0,
+ 526,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 5,
+ 4,
+ 6
+ },
+ {
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 1,
+ -1,
+ 0
+ },
+ {
+ 0,
+ 618,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 7,
+ 6,
+ 480
+ },
+ {
+ 0,
+ 589,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 6,
+ 5,
+ 1
+ },
+ {
+ 0,
+ 800,
+ (char)-8,
+ 0,
+ 48,
+ 48,
+ 9,
+ 8,
+ 35
+ }
+};
+
+MissileType missileInfo[] = {
+ // Cleaver
+ {
+ 2138,
+ 978670,
+ 512,
+ 40,
+ 40,
+ (char)-16,
+ 16,
+ 1207, 1207
+ },
+ // Regular flare
+ {
+ 2424,
+ 3145728,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 32,
+ 420, 420
+ },
+ // Tesla alt
+ {
+ 3056,
+ 2796202,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 32,
+ 471, 471
+ },
+ // Flare alt
+ {
+ 2424,
+ 2446677,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 4,
+ 421, 421
+ },
+ // Spray flame
+ {
+ 0,
+ 1118481,
+ 0,
+ 24,
+ 24,
+ (char)-128,
+ 16,
+ 1309, 351
+ },
+ // Fireball
+ {
+ 0,
+ 1118481,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 32,
+ 480, 480
+ },
+ // Tesla regular
+ {
+ 2130,
+ 2796202,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 16,
+ 470, 470
+ },
+ // EctoSkull
+ {
+ 870,
+ 699050,
+ 0,
+ 32,
+ 32,
+ (char)-24,
+ 32,
+ 489, 490
+ },
+ // Hellhound flame
+ {
+ 0,
+ 1118481,
+ 0,
+ 24,
+ 24,
+ (char)-128,
+ 16,
+ 462, 351
+ },
+ // Puke
+ {
+ 0,
+ 838860,
+ 0,
+ 16,
+ 16,
+ (char)-16,
+ 16,
+ 1203, 172
+ },
+ // Reserved
+ {
+ 0,
+ 838860,
+ 0,
+ 8,
+ 8,
+ (char)0,
+ 16,
+ 0,0
+ },
+ // Stone gargoyle projectile
+ {
+ 3056,
+ 2097152,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 16,
+ 1457, 249
+ },
+ // Napalm launcher
+ {
+ 0,
+ 2446677,
+ 0,
+ 30,
+ 30,
+ (char)-128,
+ 24,
+ 480, 489
+ },
+ // Cerberus fireball
+ {
+ 0,
+ 2446677,
+ 0,
+ 30,
+ 30,
+ (char)-128,
+ 24,
+ 480, 489
+ },
+ // Tchernobog fireball
+ {
+ 0,
+ 1398101,
+ 0,
+ 24,
+ 24,
+ (char)-128,
+ 16,
+ 480, 489
+ },
+ // Regular life leech
+ {
+ 2446,
+ 2796202,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 16,
+ 491, 491
+ },
+ // Dropped life leech (enough ammo)
+ {
+ 3056,
+ 2446677,
+ 0,
+ 16,
+ 16,
+ (char)-128,
+ 16,
+ 520, 520
+ },
+ // Dropped life leech (no ammo)
+ {
+ 3056,
+ 1747626,
+ 0,
+ 32,
+ 32,
+ (char)-128,
+ 16,
+ 520, 520
+ }
+};
+
+THINGINFO thingInfo[] = {
+ //TNT Barrel
+ {
+ 25,
+ 250,
+ 32,
+ 11,
+ 4096,
+ 80,
+ 384,
+ 907,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 256, 128, 64, 0, 0, 128,
+ 1
+ },
+ // Armed Proxy Dynamite
+ {
+ 5,
+ 5,
+ 16,
+ 3,
+ 24576,
+ 1600,
+ 256,
+ 3444,
+ (char)-16,
+ 0,
+ 32,
+ 32,
+ 256, 256, 256, 64, 0, 0, 512,
+ 1
+ },
+ // Armed Remote Dynamite
+ {
+ 5,
+ 5,
+ 16,
+ 3,
+ 24576,
+ 1600,
+ 256,
+ 3457,
+ (char)-16,
+ 0,
+ 32,
+ 32,
+ 256, 256, 256, 64, 0, 0, 512,
+ 1
+ },
+ // Vase1
+ {
+ 1,
+ 20,
+ 32,
+ 3,
+ 32768,
+ 80,
+ 0,
+ 739,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 0, 256, 128, 0, 0, 0,
+ 0
+ },
+ // Vase2
+ {
+ 1,
+ 150,
+ 32,
+ 3,
+ 32768,
+ 80,
+ 0,
+ 642,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 256, 256, 128, 0, 0, 0,
+ 0
+ },
+ // Crate face
+ {
+ 10,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 462,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0, 256, 0, 0, 0,
+ 0
+ },
+ // Glass window
+ {
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 266,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 0, 256, 256, 0, 0, 0,
+ 0,
+ },
+ // Flourescent Light
+ {
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 796,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 0, 256, 256, 0, 0, 512,
+ 0,
+ },
+ // Wall Crack
+ {
+ 50,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1127,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0, 256, 0, 0, 0,
+ 0,
+ },
+ // Wood Beam
+ {
+ 8,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1142,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 0, 256, 128, 0, 0, 0,
+ 0,
+ },
+ // Spider's Web
+ {
+ 4,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1069,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 256, 64, 256, 0, 0, 128,
+ 0,
+ },
+ // Metal Grate
+ {
+ 40,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 483,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 64, 0, 128, 256, 0, 0, 0,
+ 0,
+ },
+ // Flammable Tree
+ {
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 256, 0, 256, 0, 0, 128,
+ 0,
+ },
+ // MachineGun Trap
+ {
+ 1000,
+ 0,
+ 0,
+ 8,
+ 0,
+ 0,
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 128, 256, 0, 0, 512,
+ 0,
+ },
+ // Falling Rock
+ {
+ 0,
+ 15,
+ 8,
+ 3,
+ 32768,
+ 0,
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ },
+ // Kickable Pail
+ {
+ 0,
+ 8,
+ 48,
+ 3,
+ 49152,
+ 0,
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 1,
+ },
+ // Gib Object
+ {
+ 10,
+ 2,
+ 0,
+ 0,
+ 32768,
+ 0,
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 0, 256, 256, 0, 0, 128,
+ 0,
+ },
+ // Explode Object
+ {
+ 20,
+ 2,
+ 0,
+ 0,
+ 32768,
+ 0,
+ 0,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0, 256, 0, 0, 128,
+ 0,
+ },
+ // Armed stick Of TNT
+ {
+ 5,
+ 14,
+ 16,
+ 3,
+ 24576,
+ 1600,
+ 256,
+ 3422,
+ (char)-32,
+ 0,
+ 32,
+ 32,
+ 64, 256, 128, 64, 0, 0, 256,
+ 1
+ },
+ // Armed bundle Of TNT
+ {
+ 5,
+ 14,
+ 16,
+ 3,
+ 24576,
+ 1600,
+ 256,
+ 3433,
+ (char)-32,
+ 0,
+ 32,
+ 32,
+ 64, 256, 128, 64, 0, 0, 256,
+ 1
+ },
+ // Armed aerosol
+ {
+ 5,
+ 14,
+ 16,
+ 3,
+ 32768,
+ 1600,
+ 256,
+ 3467,
+ (char)-128,
+ 0,
+ 32,
+ 32,
+ 64, 256, 128, 64, 0, 0, 256,
+ 1
+ },
+ // Bone (Flesh Garg.)
+ {
+ 5,
+ 6,
+ 16,
+ 3,
+ 32768,
+ 1600,
+ 256,
+ 1462,
+ (char)0,
+ 0,
+ 32,
+ 32,
+ 0, 0, 0, 0, 0, 0, 0,
+ 1
+ },
+ // Some alpha stuff
+ {
+ 8,
+ 3,
+ 16,
+ 11,
+ 32768,
+ 1600,
+ 256,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 256, 0, 256, 256, 0, 0, 0,
+ 0,
+ },
+ // WaterDrip
+ {
+ 0,
+ 1,
+ 1,
+ 2,
+ 0,
+ 0,
+ 0,
+ 1147,
+ (char)0,
+ 10,
+ 0,
+ 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0
+ },
+ // BloodDrip
+ {
+ 0,
+ 1,
+ 1,
+ 2,
+ 0,
+ 0,
+ 0,
+ 1160,
+ (char)0,
+ 2,
+ 0,
+ 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ },
+ // Blood chucks1
+ {
+ 15,
+ 4,
+ 4,
+ 3,
+ 24576,
+ 0,
+ 257,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 128, 64, 256, 256, 0, 0, 256,
+ 0,
+ },
+ // Blood chucks2
+ {
+ 30,
+ 30,
+ 8,
+ 3,
+ 8192,
+ 0,
+ 257,
+ -1,
+ (char)0,
+ 0,
+ 0,
+ 0,
+ 128, 64, 256, 256, 0, 0, 64,
+ 0,
+ },
+ // Axe Zombie Head
+ {
+ 60,
+ 5,
+ 32,
+ 3,
+ 40960,
+ 1280,
+ 257,
+ 3405,
+ (char)0,
+ 0,
+ 40,
+ 40,
+ 128, 64, 256, 256, 0, 0, 64,
+ 1,
+ },
+ // Napalm's Alt Fire explosion
+ {
+ 80,
+ 30,
+ 32,
+ 3,
+ 57344,
+ 1600,
+ 256,
+ 3281,
+ (char)-128,
+ 0,
+ 32,
+ 32,
+ 0, 0, 0, 0, 0, 0, 0,
+ 1,
+ },
+ // Fire Pod Explosion
+ {
+ 80,
+ 30,
+ 32,
+ 3,
+ 57344,
+ 1600,
+ 256,
+ 2020,
+ (char)-128,
+ 0,
+ 32,
+ 32,
+ 256, 0, 256, 256, 0, 0, 0,
+ 1,
+ },
+ // Green Pod Explosion
+ {
+ 80,
+ 30,
+ 32,
+ 3,
+ 57344,
+ 1600,
+ 256,
+ 1860,
+ (char)-128,
+ 0,
+ 32,
+ 32,
+ 256, 0, 256, 256, 0, 0, 0,
+ 1,
+ },
+ // Life Leech
+ {
+ 150,
+ 30,
+ 48,
+ 3,
+ 32768,
+ 1600,
+ 257,
+ 800,
+ (char)-128,
+ 0,
+ 48,
+ 48,
+ 64, 64, 112, 64, 0, 96, 96,
+ 1,
+ },
+ // Voodoo Head
+ {
+ 1,
+ 30,
+ 48,
+ 3,
+ 32768,
+ 1600,
+ 0,
+ 2443,
+ (char)-128,
+ 0,
+ 16,
+ 16,
+ 0, 0, 0, 0, 0, 0, 0,
+ 1,
+ },
+ // 433 - kGDXThingTNTProx
+ {
+ 5,
+ 5,
+ 16,
+ 3,
+ 24576,
+ 1600,
+ 256,
+ 3444,
+ (char)-16,
+ 7,
+ 32,
+ 32,
+ 256, 256, 256, 64, 0, 0, 512,
+ 1
+ },
+ // 434 - kGDXThingThrowableRock
+ {
+ 5,
+ 6,
+ 16,
+ 3,
+ 32768,
+ 1600,
+ 256,
+ 1462,
+ (char)0,
+ 0,
+ 32,
+ 32,
+ 0, 0, 0, 0, 0, 0, 0,
+ 1
+ },
+ // 435 - kGDXThingCustomDudeLifeLeech
+ {
+ 150,
+ 30,
+ 48,
+ 3,
+ 32768,
+ 1600,
+ 257,
+ 800,
+ (char)-128,
+ 0,
+ 48,
+ 48,
+ 64, 64, 112, 64, 0, 96, 96,
+ 1,
+ },
+};
+
+EXPLOSION explodeInfo[] = {
+ {
+ 40,
+ 10,
+ 10,
+ 75,
+ 450,
+ 0,
+ 60,
+ 80,
+ 40
+ },
+ {
+ 80,
+ 20,
+ 10,
+ 150,
+ 900,
+ 0,
+ 60,
+ 160,
+ 60
+ },
+ {
+ 120,
+ 40,
+ 15,
+ 225,
+ 1350,
+ 0,
+ 60,
+ 240,
+ 80
+ },
+ {
+ 80,
+ 5,
+ 10,
+ 120,
+ 20,
+ 10,
+ 60,
+ 0,
+ 40
+ },
+ {
+ 120,
+ 10,
+ 10,
+ 180,
+ 40,
+ 10,
+ 60,
+ 0,
+ 80
+ },
+ {
+ 160,
+ 15,
+ 10,
+ 240,
+ 60,
+ 10,
+ 60,
+ 0,
+ 120
+ },
+ {
+ 40,
+ 20,
+ 10,
+ 120,
+ 0,
+ 10,
+ 30,
+ 60,
+ 40
+ },
+ {
+ 80,
+ 20,
+ 10,
+ 150,
+ 800,
+ 5,
+ 60,
+ 160,
+ 60
+ },
+};
+
+int gDudeDrag = 0x2a00;
+
+short gAffectedSectors[kMaxSectors];
+short gAffectedXWalls[kMaxXWalls];
+short gPlayerGibThingComments[] = {
+ 734, 735, 736, 737, 738, 739, 740, 741, 3038, 3049
+};
+
+void FireballSeqCallback(int, int);
+void sub_38938(int, int);
+void NapalmSeqCallback(int, int);
+void sub_3888C(int, int);
+void TreeToGibCallback(int, int);
+void DudeToGibCallback1(int, int);
+void DudeToGibCallback2(int, int);
+
+SPRITEHIT gSpriteHit[kMaxXSprites];
+
+int nFireballClient = seqRegisterClient(FireballSeqCallback);
+int dword_2192D8 = seqRegisterClient(sub_38938);
+int nNapalmClient = seqRegisterClient(NapalmSeqCallback);
+int dword_2192E0 = seqRegisterClient(sub_3888C);
+int nTreeToGibClient = seqRegisterClient(TreeToGibCallback);
+int nDudeToGibClient1 = seqRegisterClient(DudeToGibCallback1);
+int nDudeToGibClient2 = seqRegisterClient(DudeToGibCallback2);
+
+int gPostCount = 0;
+
+struct POSTPONE {
+ short at0;
+ short at2;
+};
+
+POSTPONE gPost[kMaxSprites];
+
+static char buffer[120];
+
+bool IsItemSprite(spritetype *pSprite)
+{
+ return pSprite->type >= 100 && pSprite->type < 149;
+}
+
+bool IsWeaponSprite(spritetype *pSprite)
+{
+ return pSprite->type >= 40 && pSprite->type < 51;
+}
+
+bool IsAmmoSprite(spritetype *pSprite)
+{
+ return pSprite->type >= 60 && pSprite->type < 81;
+}
+
+bool IsUnderwaterSector(int nSector)
+{
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0 && xsector[nXSector].Underwater)
+ return 1;
+ return 0;
+}
+
+int actSpriteOwnerToSpriteId(spritetype *pSprite)
+{
+ dassert(pSprite != NULL);
+ if (pSprite->owner == -1)
+ return -1;
+ int nSprite = pSprite->owner & 0xfff;
+ if (pSprite->owner & 0x1000)
+ nSprite = gPlayer[nSprite].pSprite->index;
+ return nSprite;
+}
+
+void actPropagateSpriteOwner(spritetype *pTarget, spritetype *pSource)
+{
+ dassert(pTarget != NULL && pSource != NULL);
+ if (IsPlayerSprite(pSource))
+ pTarget->owner = (pSource->type-kDudePlayer1) | 0x1000;
+ else
+ pTarget->owner = pSource->index;
+}
+
+int actSpriteIdToOwnerId(int nSprite)
+{
+ if (nSprite == -1)
+ return -1;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ if (IsPlayerSprite(pSprite))
+ nSprite = (pSprite->type-kDudePlayer1) | 0x1000;
+ return nSprite;
+}
+
+int actOwnerIdToSpriteId(int nSprite)
+{
+ if (nSprite == -1)
+ return -1;
+ if (nSprite & 0x1000)
+ nSprite = gPlayer[nSprite&0xfff].pSprite->index;
+ return nSprite;
+}
+
+bool actTypeInSector(int nSector, int nType)
+{
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ if (sprite[nSprite].index == nType)
+ return 1;
+ }
+ return 0;
+}
+
+void actAllocateSpares(void)
+{
+}
+
+int DudeDifficulty[5] = {
+ 512, 384, 256, 208, 160
+};
+
+void actInit(void)
+{
+ for (int nSprite = headspritestat[3]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->type == 44) // Voodoo doll (ammo)
+ pSprite->type = 70; // Voodoo doll (weapon)
+
+ switch (pSprite->type) {
+ case 44:
+ pSprite->type = 70;
+ break;
+
+ // By NoOne: add Random pickup feature
+ case 40: // Random weapon
+ case 80: // Random ammo
+
+ // Make sprites invisible and non-blocking
+ pSprite->cstat &= ~kSprBlock;
+ pSprite->cstat |= kSprInvisible;
+
+ if (pSprite->extra > 0 && xsprite[pSprite->extra].state == 1)
+ trTriggerSprite(nSprite, &xsprite[pSprite->extra], COMMAND_ID_0);
+ break;
+ }
+ }
+ for (int nSprite = headspritestat[11]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = NULL;
+ if (nXSprite > 0 && nXSprite < kMaxXSprites)
+ pXSprite = &xsprite[nXSprite];
+ if (pSprite->type == 459)
+ {
+ pXSprite->state = 0;
+ pXSprite->waitTime = ClipLow(pXSprite->waitTime, 1);
+ pSprite->cstat &= ~1;
+ pSprite->cstat |= 32768;
+ }
+ }
+ for (int nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ if (nXSprite <= 0 || nXSprite >= kMaxXSprites)
+ ThrowError("WARNING: Sprite %d is on the wrong status list!\n", nSprite);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nType = pSprite->type - 400;
+ pSprite->clipdist = thingInfo[nType].at4;
+ pSprite->hitag = thingInfo[nType].at5;
+ if (pSprite->hitag&2)
+ pSprite->hitag |= 4;
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+ pXSprite->health = thingInfo[nType].at0<<4;
+ switch (pSprite->type)
+ {
+ case 401:
+ case 413:
+ case kGDXThingTNTProx:
+ pXSprite->state = 0;
+ break;
+ case 426:
+ {
+ SEQINST *pInst = GetInstance(3, nXSprite);
+ if (pInst && pInst->at13)
+ {
+ DICTNODE *hSeq = gSysRes.Lookup(pInst->at8, "SEQ");
+ if (!hSeq)
+ break;
+ seqSpawn(pInst->at8, 3, nXSprite);
+ }
+ break;
+ }
+ default:
+ pXSprite->state = 1;
+ break;
+ }
+ }
+ if (gGameOptions.nMonsterSettings == 0)
+ {
+ gKillMgr.SetCount(0);
+ while (headspritestat[6] >= 0)
+ {
+ spritetype *pSprite = &sprite[headspritestat[6]];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ // Drop Key
+ if (pXSprite->key > 0)
+ actDropObject(pSprite, 99 + pXSprite->key);
+ DeleteSprite(headspritestat[6]);
+ }
+ }
+ else
+ {
+ char unk[kDudeMax-kDudeBase];
+ memset(unk, 0, sizeof(unk));
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax)
+ ThrowError("Non-enemy sprite (%d) in the enemy sprite list.\n", nSprite);
+ unk[pSprite->type-kDudeBase] = 1;
+ }
+ gKillMgr.sub_2641C();
+ for (int i = 0; i < kDudeMax-kDudeBase; i++)
+ for (int j = 0; j < 7; j++)
+ dudeInfo[i].at70[j] = mulscale8(DudeDifficulty[gGameOptions.nDifficulty], dudeInfo[i].startDamage[j]);
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nType = pSprite->type-kDudeBase;
+ if (!IsPlayerSprite(pSprite))
+ {
+ pSprite->cstat |= 4096+256+1;
+
+ // By NoOne: allow user clipdist for custom dude.
+ switch (pSprite->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ break;
+ default:
+ pSprite->clipdist = dudeInfo[nType].clipdist;
+ break;
+ }
+
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+
+ // By NoOne: add a way to set custom hp for every enemy - should work only if map just started and not loaded.
+ if (pXSprite->data4 <= 0) pXSprite->health = dudeInfo[nType].startHealth << 4;
+ else {
+ long hp = pXSprite->data4 << 4;
+ pXSprite->health = (hp > 0) ? ((hp <= 65535) ? hp : 65535) : 1;
+ }
+ }
+
+ int seqStartId = dudeInfo[nType].seqStartID;
+ // By NoOne: store seqStartId in data2 field for custom dude
+ if (pSprite->type == kGDXDudeUniversalCultist) {
+
+ if (pXSprite->data2 > 0) {
+ seqStartId = pXSprite->data2;
+ int seqEndId = pXSprite->data2 + 19;
+
+ // check for full set of animations
+ for (int i = seqStartId; i <= seqEndId; i++) {
+
+ // exceptions
+ switch (i - seqStartId) {
+ case 3:
+ case 4:
+ case 11:
+ case 12:
+ case 18:
+ case 19:
+ continue;
+ }
+
+ if (!gSysRes.Lookup(i, "SEQ")) {
+ //ThrowError("No SEQ file found for custom dude!");
+ pXSprite->data2 = dudeInfo[nType].seqStartID;
+ seqStartId = pXSprite->data2;
+ break;
+ }
+ }
+
+ } else {
+ pXSprite->data2 = seqStartId;
+ }
+ }
+
+ if (gSysRes.Lookup(seqStartId, "SEQ")) seqSpawn(seqStartId, 3, nXSprite);
+ }
+ aiInit();
+ }
+}
+
+void ConcussSprite(int a1, spritetype *pSprite, int x, int y, int z, int a6)
+{
+ dassert(pSprite != NULL);
+ int dx = pSprite->x-x;
+ int dy = pSprite->y-y;
+ int dz = (pSprite->z-z)>>4;
+ int dist2 = 0x40000+dx*dx+dy*dy+dz*dz;
+ dassert(dist2 > 0);
+ a6 = scale(0x40000, a6, dist2);
+ if (pSprite->hitag & 1)
+ {
+ int mass = 0;
+ if (IsDudeSprite(pSprite)) {
+ mass = dudeInfo[pSprite->lotag - kDudeBase].mass;
+ switch (pSprite->lotag) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ mass = getDudeMassBySpriteSize(pSprite);
+ break;
+ }
+ }
+ else if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
+ mass = thingInfo[pSprite->type-400].at2;
+ else
+ ThrowError("Unexpected type in ConcussSprite(): Sprite: %d Type: %d Stat: %d", (int)pSprite->index, (int)pSprite->type, (int)pSprite->statnum);
+ int size = (tilesiz[pSprite->picnum].x*pSprite->xrepeat*tilesiz[pSprite->picnum].y*pSprite->yrepeat)>>1;
+ dassert(mass > 0);
+
+ int t = scale(a6, size, mass);
+ dx = mulscale16(t, dx);
+ dy = mulscale16(t, dy);
+ dz = mulscale16(t, dz);
+ int nSprite = pSprite->index;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ xvel[nSprite] += dx;
+ yvel[nSprite] += dy;
+ zvel[nSprite] += dz;
+ }
+ actDamageSprite(a1, pSprite, DAMAGE_TYPE_3, a6);
+}
+
+int actWallBounceVector(int *x, int *y, int nWall, int a4)
+{
+ int wx, wy;
+ GetWallNormal(nWall, &wx, &wy);
+ int t = dmulscale16(*x, wx, *y, wy);
+ int t2 = mulscale16r(t, a4+0x10000);
+ *x -= mulscale16(wx, t2);
+ *y -= mulscale16(wy, t2);
+ return mulscale16r(t, 0x10000-a4);
+}
+
+int actFloorBounceVector(int *x, int *y, int *z, int nSector, int a5)
+{
+ int t = 0x10000-a5;
+ if (sector[nSector].floorheinum == 0)
+ {
+ int t2 = mulscale16(*z, t);
+ *z = -(*z-t2);
+ return t2;
+ }
+ walltype *pWall = &wall[sector[nSector].wallptr];
+ walltype *pWall2 = &wall[pWall->point2];
+ int angle = getangle(pWall2->x-pWall->x, pWall2->y-pWall->y)+512;
+ int t2 = sector[nSector].floorheinum<<4;
+ int t3 = approxDist(-0x10000, t2);
+ int t4 = divscale16(-0x10000, t3);
+ int t5 = divscale16(t2, t3);
+ int t6 = mulscale30(t5, Cos(angle));
+ int t7 = mulscale30(t5, Sin(angle));
+ int t8 = tmulscale16(*x, t6, *y, t7, *z, t4);
+ int t9 = mulscale16(t8, 0x10000+a5);
+ *x -= mulscale16(t6, t9);
+ *y -= mulscale16(t7, t9);
+ *z -= mulscale16(t4, t9);
+ return mulscale16r(t8, t);
+}
+
+void sub_2A620(int nSprite, int x, int y, int z, int nSector, int nDist, int a7, int a8, DAMAGE_TYPE a9, int a10, int a11, int a12, int a13)
+{
+ UNREFERENCED_PARAMETER(a12);
+ UNREFERENCED_PARAMETER(a13);
+ char va0[(kMaxSectors+7)>>3];
+ int nOwner = actSpriteIdToOwnerId(nSprite);
+ gAffectedSectors[0] = 0;
+ gAffectedXWalls[0] = 0;
+ GetClosestSpriteSectors(nSector, x, y, nDist, gAffectedSectors, va0, gAffectedXWalls);
+ nDist <<= 4;
+ if (a10 & 2)
+ {
+ for (int i = headspritestat[6]; i >= 0; i = nextspritestat[i])
+ {
+ if (i != nSprite || (a10 & 1))
+ {
+ spritetype *pSprite2 = &sprite[i];
+ if (pSprite2->extra > 0 && pSprite2->extra < kMaxXSprites)
+ {
+ if (pSprite2->hitag & 0x20)
+ continue;
+ if (!TestBitString(va0, pSprite2->sectnum))
+ continue;
+ if (!CheckProximity(pSprite2, x, y, z, nSector, nDist))
+ continue;
+ int dx = klabs(x-pSprite2->x);
+ int dy = klabs(y-pSprite2->y);
+ int dz = klabs(z-pSprite2->z)>>4;
+ int dist = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (dist > nDist)
+ continue;
+ int vcx;
+ if (dist != 0)
+ vcx = a7+((nDist-dist)*a8)/nDist;
+ else
+ vcx = a7+a8;
+ actDamageSprite(nSprite, pSprite2, a9, vcx<<4);
+ if (a11)
+ actBurnSprite(nOwner, &xsprite[pSprite2->extra], a11);
+ }
+ }
+ }
+ }
+ if (a10 & 4)
+ {
+ for (int i = headspritestat[4]; i >= 0; i = nextspritestat[i])
+ {
+ spritetype *pSprite2 = &sprite[i];
+ if (pSprite2->hitag&0x20)
+ continue;
+ if (!TestBitString(va0, pSprite2->sectnum))
+ continue;
+ if (!CheckProximity(pSprite2, x, y, z, nSector, nDist))
+ continue;
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ if (pXSprite2->locked)
+ continue;
+ int dx = klabs(x-pSprite2->x);
+ int dy = klabs(y-pSprite2->y);
+ int dist = ksqrt(dx*dx+dy*dy);
+ if (dist > nDist)
+ continue;
+ int vcx;
+ if (dist != 0)
+ vcx = a7+((nDist-dist)*a8)/nDist;
+ else
+ vcx = a7+a8;
+ actDamageSprite(nSprite, pSprite2, a9, vcx<<4);
+ if (a11)
+ actBurnSprite(nOwner, pXSprite2, a11);
+ }
+ }
+}
+
+void sub_2AA94(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = actOwnerIdToSpriteId(pSprite->owner);
+ actPostSprite(pSprite->index, 0);
+ seqSpawn(9, 3, pSprite->extra);
+ if (Chance(0x8000))
+ pSprite->cstat |= 4;
+ sfxPlay3DSound(pSprite, 303, 24+(pSprite->hitag&3), 1);
+ sub_2A620(nSprite, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 128, 0, 60, DAMAGE_TYPE_3, 15, 120, 0, 0);
+ if (pXSprite->data4 > 1)
+ {
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ int v14[2];
+ v14[0] = pXSprite->data4>>1;
+ v14[1] = pXSprite->data4-v14[0];
+ int v4 = pSprite->ang;
+ xvel[pSprite->index] = 0;
+ yvel[pSprite->index] = 0;
+ zvel[pSprite->index] = 0;
+ for (int i = 0; i < 2; i++)
+ {
+ int t1 = Random(0x33333)+0x33333;
+ int t2 = Random2(0x71);
+ pSprite->ang = (t2+v4+2048)&2047;
+ spritetype *pSprite2 = actFireThing(pSprite, 0, 0, -0x93d0, 428, t1);
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ pSprite2->owner = pSprite->owner;
+ seqSpawn(61, 3, pSprite2->extra, nNapalmClient);
+ pXSprite2->data4 = v14[i];
+ }
+ }
+}
+
+spritetype *actSpawnFloor(spritetype *pSprite)
+{
+ short nSector = pSprite->sectnum;
+ int x = pSprite->x;
+ int y = pSprite->y;
+ updatesector(x, y, &nSector);
+ int zFloor = getflorzofslope(nSector, x, y);
+ spritetype *pSprite2 = actSpawnSprite(nSector, x, y, zFloor, 3, 0);
+ if (pSprite2)
+ pSprite2->cstat &= ~257;
+ return pSprite2;
+}
+
+spritetype *actDropAmmo(spritetype *pSprite, int nType)
+{
+ spritetype *pSprite2 = NULL;
+ if (pSprite && pSprite->statnum < kMaxStatus && nType >= 60 && nType < 81)
+ {
+ pSprite2 = actSpawnFloor(pSprite);
+ AMMOITEMDATA *pAmmo = &gAmmoItemData[nType-60];
+ pSprite2->type = nType;
+ pSprite2->picnum = pAmmo->picnum;
+ pSprite2->shade = pAmmo->shade;
+ pSprite2->xrepeat = pAmmo->xrepeat;
+ pSprite2->yrepeat = pAmmo->yrepeat;
+ }
+ return pSprite2;
+}
+
+spritetype *actDropWeapon(spritetype *pSprite, int nType)
+{
+ spritetype *pSprite2 = NULL;
+ if (pSprite && pSprite->statnum < kMaxStatus && nType >= 40 && nType < 51)
+ {
+ pSprite2 = actSpawnFloor(pSprite);
+ WEAPONITEMDATA *pWeapon = &gWeaponItemData[nType-40];
+ pSprite2->type = nType;
+ pSprite2->picnum = pWeapon->picnum;
+ pSprite2->shade = pWeapon->shade;
+ pSprite2->xrepeat = pWeapon->xrepeat;
+ pSprite2->yrepeat = pWeapon->yrepeat;
+ }
+ return pSprite2;
+}
+
+spritetype *actDropItem(spritetype *pSprite, int nType)
+{
+ spritetype *pSprite2 = NULL;
+ if (pSprite && pSprite->statnum < kMaxStatus && nType >= 100 && nType < 149)
+ {
+ pSprite2 = actSpawnFloor(pSprite);
+ ITEMDATA *pItem = &gItemData[nType-100];
+ pSprite2->type = nType;
+ pSprite2->picnum = pItem->picnum;
+ pSprite2->shade = pItem->shade;
+ pSprite2->xrepeat = pItem->xrepeat;
+ pSprite2->yrepeat = pItem->yrepeat;
+ }
+ return pSprite2;
+}
+
+spritetype *actDropKey(spritetype *pSprite, int nType)
+{
+ spritetype *pSprite2 = NULL;
+ if (pSprite && pSprite->statnum < kMaxStatus && nType >= 100 && nType <= 106)
+ {
+ pSprite2 = actDropItem(pSprite, nType);
+ if (pSprite2 && gGameOptions.nGameType == 1)
+ {
+ if (pSprite2->extra == -1)
+ dbInsertXSprite(pSprite2->index);
+ xsprite[pSprite2->extra].respawn = 3;
+ gSpriteHit[pSprite2->extra].florhit = 0;
+ gSpriteHit[pSprite2->extra].ceilhit = 0;
+ }
+ }
+ return pSprite2;
+}
+
+spritetype *actDropFlag(spritetype *pSprite, int nType)
+{
+ spritetype *pSprite2 = NULL;
+ if (pSprite && pSprite->statnum < kMaxStatus && (nType == 147 || nType == 148))
+ {
+ pSprite2 = actDropItem(pSprite, nType);
+ if (pSprite2 && gGameOptions.nGameType == 3)
+ {
+ evPost(pSprite2->index, 3, 1800, CALLBACK_ID_17);
+ }
+ }
+ return pSprite2;
+}
+
+spritetype *actDropObject(spritetype *pSprite, int nType)
+{
+ spritetype *pSprite2 = NULL;
+ if (nType >= 100 && nType <= 106)
+ pSprite2 = actDropKey(pSprite, nType);
+ else if (nType == 147 || nType == 148)
+ pSprite2 = actDropFlag(pSprite, nType);
+ else if (nType >= 100 && nType < 149)
+ pSprite2 = actDropItem(pSprite, nType);
+ else if (nType >= 60 && nType < 81)
+ pSprite2 = actDropAmmo(pSprite, nType);
+ else if (nType >= 40 && nType < 51)
+ pSprite2 = actDropWeapon(pSprite, nType);
+ if (pSprite2)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (bottom >= pSprite2->z)
+ pSprite2->z -= bottom - pSprite2->z;
+ }
+ return pSprite2;
+}
+
+bool actHealDude(XSPRITE *pXDude, int a2, int a3)
+{
+ dassert(pXDude != NULL);
+ a2 <<= 4;
+ a3 <<= 4;
+ if (pXDude->health < a3)
+ {
+ spritetype *pSprite = &sprite[pXDude->reference];
+ if (IsPlayerSprite(pSprite))
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 780, pSprite->sectnum);
+ pXDude->health = ClipHigh(pXDude->health+a2, a3);
+ return 1;
+ }
+ return 0;
+}
+
+void actKillDude(int a1, spritetype *pSprite, DAMAGE_TYPE a3, int a4)
+{
+ spritetype *pSprite2 = &sprite[a1];
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ int nType = pSprite->type-kDudeBase;
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0);
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ switch (pSprite->type)
+ {
+ case kGDXDudeUniversalCultist:
+ {
+ removeDudeStuff(pSprite);
+ XSPRITE* pXIncarnation = getNextIncarnation(pXSprite);
+ if (pXIncarnation == NULL) {
+
+ if (pXSprite->data1 >= 459 && pXSprite->data1 < (459 + kExplodeMax) - 1 &&
+ Chance(0x4000) && a3 != 5 && a3 != 4) {
+
+ doExplosion(pSprite, pXSprite->data1 - 459);
+ if (Chance(0x9000)) a3 = (DAMAGE_TYPE) 3;
+ }
+
+ if (a3 == DAMAGE_TYPE_1) {
+ if ((gSysRes.Lookup(pXSprite->data2 + 15, "SEQ") || gSysRes.Lookup(pXSprite->data2 + 16, "SEQ")) && pXSprite->medium == 0) {
+ if (gSysRes.Lookup(pXSprite->data2 + 3, "SEQ")) {
+ pSprite->type = kGDXGenDudeBurning;
+ if (pXSprite->data2 == 11520) // don't inherit palette for burning if using default animation
+ pSprite->pal = 0;
+
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnGoto);
+ actHealDude(pXSprite, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
+ if (pXSprite->burnTime <= 0) pXSprite->burnTime = 1200;
+ gDudeExtra[pSprite->extra].at0 = gFrameClock + 360;
+ return;
+ }
+
+ } else {
+ pXSprite->burnTime = 0;
+ pXSprite->burnSource = -1;
+ a3 = DAMAGE_TYPE_0;
+ }
+ }
+
+ } else {
+ int seqId = pXSprite->data2 + 18;
+ if (!gSysRes.Lookup(seqId, "SEQ")) {
+ seqKill(3, nXSprite);
+ sfxPlayGDXGenDudeSound(pSprite, 10, pXSprite->data3);
+ spritetype* pEffect = gFX.fxSpawn((FX_ID)52, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, pSprite->ang);
+ if (pEffect != NULL) {
+ pEffect->cstat = kSprFace;
+ pEffect->pal = 6;
+ pEffect->xrepeat = pSprite->xrepeat;
+ pEffect->yrepeat = pSprite->yrepeat;
+ }
+
+ GIBTYPE nGibType;
+ for (int i = 0; i < 3; i++) {
+ if (Chance(0x3000)) nGibType = GIBTYPE_6;
+ else if (Chance(0x2000)) nGibType = GIBTYPE_5;
+ else nGibType = GIBTYPE_17;
+
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ CGibPosition gibPos(pSprite->x, pSprite->y, top);
+ CGibVelocity gibVel(xvel[pSprite->index] >> 1, yvel[pSprite->index] >> 1, -0xccccc);
+ GibSprite(pSprite, nGibType, &gibPos, &gibVel);
+ }
+
+ return;
+ }
+ seqSpawn(seqId, 3, nXSprite, -1);
+ sfxPlayGDXGenDudeSound(pSprite, 10, pXSprite->data3);
+ return;
+ }
+ break;
+ }
+ case 227: // Cerberus
+ seqSpawn(dudeInfo[nType].seqStartID+1, 3, nXSprite, -1);
+ return;
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ if (a3 == DAMAGE_TYPE_1 && pXSprite->medium == 0)
+ {
+ pSprite->type = 240;
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ actHealDude(pXSprite, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
+ return;
+ }
+ // no break
+ fallthrough__;
+ case 251:
+ if (a3 == DAMAGE_TYPE_1 && pXSprite->medium == 0)
+ {
+ pSprite->type = 253;
+ aiNewState(pSprite, pXSprite, &beastBurnGoto);
+ actHealDude(pXSprite, dudeInfo[53].startHealth, dudeInfo[53].startHealth);
+ return;
+ }
+ // no break
+ fallthrough__;
+ case 245:
+ if (a3 == DAMAGE_TYPE_1 && pXSprite->medium == 0)
+ {
+ pSprite->type = 239;
+ aiNewState(pSprite, pXSprite, &innocentBurnGoto);
+ actHealDude(pXSprite, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
+ return;
+ }
+ break;
+ }
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (gPlayer[p].at2ee == pSprite->index && gPlayer[p].at1fe > 0)
+ gPlayer[p].at2ee = -1;
+ }
+ if (pSprite->type != 249)
+ trTriggerSprite(pSprite->index, pXSprite, 0);
+ pSprite->hitag |= 7;
+ if (IsPlayerSprite(pSprite2))
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite2->index-kDudePlayer1];
+ if (gGameOptions.nGameType == 1)
+ pPlayer->at2c6++;
+ }
+
+ if (pXSprite->key > 0)
+ actDropObject(pSprite, 100+pXSprite->key-1);
+ if (pXSprite->dropMsg > 0)
+ actDropObject(pSprite, pXSprite->dropMsg);
+ if (pSprite->type == 201)
+ {
+ int nRand = Random(100);
+ if (nRand < 10)
+ actDropObject(pSprite, 42);
+ else if (nRand < 50)
+ actDropObject(pSprite, 69);
+ }
+ else if (pSprite->type == 202)
+ {
+ int nRand = Random(100);
+ if (nRand <= 10)
+ actDropObject(pSprite, 41);
+ else if (nRand <= 50)
+ actDropObject(pSprite, 67);
+ }
+ int nSeq;
+ switch (a3)
+ {
+ case DAMAGE_TYPE_3:
+ nSeq = 2;
+ switch (pSprite->type)
+ {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ sfxPlayGDXGenDudeSound(pSprite, 4, pXSprite->data3);
+ break;
+ case 201:
+ case 202:
+ case 230:
+ case 239:
+ case 240:
+ case 245:
+ case 246:
+ case 247:
+ case 248:
+ case 249:
+ case 250:
+ case 252:
+ sfxPlay3DSound(pSprite, 717,-1,0);
+ break;
+ }
+ break;
+ case DAMAGE_TYPE_1:
+ nSeq = 3;
+ sfxPlay3DSound(pSprite, 351, -1, 0);
+ break;
+ case DAMAGE_TYPE_5:
+ switch (pSprite->type)
+ {
+ case 203:
+ case 205:
+ nSeq = 14;
+ break;
+ case 204:
+ nSeq = 11;
+ break;
+ default:
+ nSeq = 1;
+ break;
+ }
+ break;
+ case DAMAGE_TYPE_0:
+ switch (pSprite->type)
+ {
+ case 201:
+ case 202:
+ nSeq = 1;
+ break;
+ default:
+ nSeq = 1;
+ break;
+ }
+ break;
+ default:
+ nSeq = 1;
+ break;
+ }
+
+ if (!gSysRes.Lookup(dudeInfo[nType].seqStartID + nSeq, "SEQ"))
+ {
+ seqKill(3, nXSprite);
+ gKillMgr.AddKill(pSprite);
+ actPostSprite(pSprite->index, 1024);
+ return;
+ }
+
+ switch (pSprite->type)
+ {
+ case 203:
+ sfxPlay3DSound(pSprite, 1107+Random(2), -1, 0);
+ if (nSeq == 2)
+ {
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ CGibPosition gibPos(pSprite->x, pSprite->y, top);
+ CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc);
+ GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
+ }
+ else if (nSeq == 1 && Chance(0x4000))
+ {
+ seqSpawn(dudeInfo[nType].seqStartID+7, 3, nXSprite, nDudeToGibClient1);
+ evPost(pSprite->index, 3, 0, CALLBACK_ID_5);
+ sfxPlay3DSound(pSprite, 362, -1, 0);
+ pXSprite->data1 = 35;
+ pXSprite->data2 = 5;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ CGibPosition gibPos(pSprite->x, pSprite->y, top);
+ CGibVelocity gibVel(xvel[pSprite->index] >> 1, yvel[pSprite->index] >> 1, -0x111111);
+ GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
+ }
+ else if (nSeq == 14)
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ else if (nSeq == 3)
+ seqSpawn(dudeInfo[nType].seqStartID+13, 3, nXSprite, nDudeToGibClient2);
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
+ break;
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ sfxPlay3DSound(pSprite, 1018+Random(2), -1, 0);
+ if (nSeq == 3)
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient2);
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
+ break;
+ case 240:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 718, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1018+Random(2), -1, 0);
+ a3 = DAMAGE_TYPE_3;
+ if (Chance(0x8000))
+ {
+ for (int i = 0; i < 3; i++)
+ GibSprite(pSprite, GIBTYPE_7, NULL, NULL);
+ seqSpawn(dudeInfo[nType].seqStartID+16-Random(1), 3, nXSprite, nDudeToGibClient1);
+ }
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+15, 3, nXSprite, nDudeToGibClient2);
+ break;
+ case kGDXDudeUniversalCultist:
+ sfxPlayGDXGenDudeSound(pSprite, 2, pXSprite->data3);
+ if (nSeq == 3) {
+ if (gSysRes.Lookup(pXSprite->data2 + 3, "SEQ")) seqSpawn(3 + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
+ else if (gSysRes.Lookup(pXSprite->data2 + 16, "SEQ")) seqSpawn((15 + Random(2)) + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
+ else seqSpawn(15 + pXSprite->data2, 3, nXSprite, nDudeToGibClient2);
+
+ } else {
+ seqSpawn(nSeq + pXSprite->data2, 3, nXSprite, nDudeToGibClient1);
+ }
+
+ pXSprite->txID = 0; // to avoid second trigger.
+ break;
+
+ case kGDXGenDudeBurning:
+ {
+ sfxPlayGDXGenDudeSound(pSprite, 4, pXSprite->data3);
+ a3 = DAMAGE_TYPE_3;
+
+ if (Chance(0x4000)) {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ CGibPosition gibPos(pSprite->x, pSprite->y, top);
+ CGibVelocity gibVel(xvel[pSprite->index] >> 1, yvel[pSprite->index] >> 1, -0xccccc);
+ GibSprite(pSprite, GIBTYPE_7, &gibPos, &gibVel);
+ }
+
+ int seqId = pXSprite->data2;
+ if (gSysRes.Lookup(pXSprite->data2 + 16, "SEQ")) seqSpawn(seqId += 15 + Random(2), 3, nXSprite, nDudeToGibClient1);
+ else seqSpawn(seqId += 15, 3, nXSprite, nDudeToGibClient1);
+ break;
+ }
+ case 241:
+ if (Chance(0x8000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1109, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1107+Random(2), -1, 0);
+ a3 = DAMAGE_TYPE_3;
+ if (Chance(0x8000))
+ {
+ seqSpawn(dudeInfo[nType].seqStartID+13, 3, nXSprite, nDudeToGibClient1);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ CGibPosition gibPos(pSprite->x, pSprite->y, top);
+ CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc);
+ GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
+ }
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+13, 3, nXSprite, nDudeToGibClient2);
+ break;
+ case 242:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1206, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1204+Random(2), -1, 0);
+ seqSpawn(dudeInfo[4].seqStartID+10, 3, nXSprite, -1);
+ break;
+ case 239:
+ a3 = DAMAGE_TYPE_3;
+ seqSpawn(dudeInfo[nType].seqStartID+7, 3, nXSprite, nDudeToGibClient1);
+ break;
+ case 204:
+ if (nSeq == 14)
+ {
+ sfxPlay3DSound(pSprite, 1206, -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+11, 3, nXSprite, -1);
+ break;
+ }
+ sfxPlay3DSound(pSprite, 1204+Random(2), -1, 0);
+ if (nSeq == 3)
+ seqSpawn(dudeInfo[nType].seqStartID+10, 3, nXSprite, -1);
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 206:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1405, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1403+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 207:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1455, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1453+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 210:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1605, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1603+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 211:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1305, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1303+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 212:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1905, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1903+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 213:
+ if (pSprite->owner != -1)
+ {
+ spritetype *pOwner = &sprite[actSpriteOwnerToSpriteId(pSprite)];
+ gDudeExtra[pOwner->extra].at6.u1.at4--;
+ }
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1805, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1803+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 214:
+ if (pSprite->owner != -1)
+ {
+ spritetype *pOwner = &sprite[actSpriteOwnerToSpriteId(pSprite)];
+ gDudeExtra[pOwner->extra].at6.u1.at4--;
+ }
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1805, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1803+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 215:
+ if (pSprite->owner != -1)
+ {
+ spritetype *pOwner = &sprite[actSpriteOwnerToSpriteId(pSprite)];
+ gDudeExtra[pOwner->extra].at6.u1.at4--;
+ }
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1805, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1803+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 216:
+ sfxPlay3DSound(pSprite, 1850, -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 217:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1705, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1703+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 218:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 1505, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1503+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 219:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2005, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2003+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 220:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2105, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2103+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 221:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2205, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2203+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 222:
+ if (a4 == 5)
+ sfxPlay3DSound(pSprite, 2471, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2472, -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 223:
+ if (a4 == 5)
+ sfxPlay3DSound(pSprite, 2451, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2452, -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 224:
+ sfxPlay3DSound(pSprite, 2501, -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 225:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2205, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2203+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 226:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2205, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2203+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 227:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2305, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2305+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 228:
+ if (Chance(0x4000) && nSeq == 3)
+ sfxPlay3DSound(pSprite, 2305, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2305+Random(2), -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 229:
+ sfxPlay3DSound(pSprite, 2380, -1, 0);
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ case 252:
+ a3 = DAMAGE_TYPE_3;
+ seqSpawn(dudeInfo[nType].seqStartID+11, 3, nXSprite, nDudeToGibClient1);
+ break;
+ case 251:
+ sfxPlay3DSound(pSprite, 9000+Random(2), -1, 0);
+ if (nSeq == 3)
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient2);
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, nDudeToGibClient1);
+ break;
+ case 253:
+ a3 = DAMAGE_TYPE_3;
+ seqSpawn(dudeInfo[nType].seqStartID+12, 3, nXSprite, nDudeToGibClient1);
+ break;
+ default:
+ seqSpawn(dudeInfo[nType].seqStartID+nSeq, 3, nXSprite, -1);
+ break;
+ }
+
+ // kMaxSprites = custom dude had once life leech
+ if (pSprite->owner != -1 && pSprite->owner != kMaxSprites) {
+ //int owner = actSpriteIdToOwnerId(pSprite->xvel);
+ int owner = pSprite->owner;
+ switch (sprite[owner].lotag) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ if (owner != -1) gDudeExtra[sprite[owner].extra].at6.u1.at4--;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (a3 == DAMAGE_TYPE_3)
+ {
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ for (int i = 0; i < 3; i++)
+ if (pDudeInfo->nGibType[i] > -1)
+ GibSprite(pSprite, (GIBTYPE)pDudeInfo->nGibType[i], NULL, NULL);
+ for (int i = 0; i < 4; i++)
+ fxSpawnBlood(pSprite, a4);
+ }
+ gKillMgr.AddKill(pSprite);
+ actCheckRespawn(pSprite);
+ pSprite->type = 426;
+ actPostSprite(pSprite->index, 4);
+}
+
+int actDamageSprite(int nSource, spritetype *pSprite, DAMAGE_TYPE a3, int a4)
+{
+ dassert(nSource < kMaxSprites);
+ if (pSprite->hitag&32)
+ return 0;
+ int nXSprite = pSprite->extra;
+ if (nXSprite <= 0)
+ return 0;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ dassert(pXSprite->reference == pSprite->index);
+ if ((pXSprite->health == 0 && pSprite->statnum != 6) || pXSprite->locked)
+ return 0;
+ if (nSource == -1)
+ nSource = pSprite->index;
+ PLAYER *pSourcePlayer = NULL;
+ if (IsPlayerSprite(&sprite[nSource]))
+ pSourcePlayer = &gPlayer[sprite[nSource].type-kDudePlayer1];
+ switch (pSprite->statnum)
+ {
+ case 6:
+ {
+ if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax)
+ {
+ sprintf(buffer, "Bad Dude Failed: initial=%d type=%d %s\n", (int)pSprite->zvel, (int)pSprite->type, (int)(pSprite->hitag&16) ? "RESPAWN" : "NORMAL");
+ ThrowError(buffer);
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ int nType = pSprite->type-kDudeBase;
+ int nDamageFactor = dudeInfo[nType].at70[a3];
+ if (!nDamageFactor)
+ return 0;
+ if (nDamageFactor != 256)
+ a4 = mulscale8(a4, nDamageFactor);
+ if (!IsPlayerSprite(pSprite))
+ {
+ if (!pXSprite->health)
+ return 0;
+ a4 = aiDamageSprite(pSprite, pXSprite, nSource, a3, a4);
+ if (!pXSprite->health)
+ {
+ if (a3 == DAMAGE_TYPE_3 && a4 < 160)
+ a3 = DAMAGE_TYPE_0;
+ actKillDude(nSource, pSprite, a3, a4);
+ }
+ }
+ else
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ if (pXSprite->health > 0 || playerSeqPlaying(pPlayer, 16))
+ a4 = playerDamageSprite(nSource, pPlayer, a3, a4);
+ }
+ break;
+ }
+ case 4:
+ dassert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
+ int nType = pSprite->type-kThingBase;
+ int nDamageFactor = thingInfo[nType].at17[a3];
+ if (!nDamageFactor)
+ return 0;
+ if (nDamageFactor != 256)
+ a4 = mulscale8(a4, nDamageFactor);
+ pXSprite->health = ClipLow(pXSprite->health-a4, 0);
+ if (!pXSprite->health)
+ {
+ if (pSprite->type == 431 || pSprite->type == kGDXThingCustomDudeLifeLeech)
+ {
+ GibSprite(pSprite, GIBTYPE_14, NULL, NULL);
+ pXSprite->data1 = 0;
+ pXSprite->data2 = 0;
+ pXSprite->data3 = 0;
+ pXSprite->stateTimer = 0;
+ pXSprite->data4 = 0;
+ pXSprite->isTriggered = 0;
+ pXSprite->DudeLockout = 0;
+
+ if (pSprite->owner >= 0 && sprite[pSprite->owner].type == kGDXDudeUniversalCultist)
+ sprite[pSprite->owner].owner = kMaxSprites; // By NoOne: indicates if custom dude had life leech.
+ }
+ else if (!(pSprite->hitag&16))
+ actPropagateSpriteOwner(pSprite, &sprite[nSource]);
+ trTriggerSprite(pSprite->index, pXSprite, 0);
+ switch (pSprite->type)
+ {
+ case 416:
+ case 417:
+ case 425:
+ case 426:
+ case 427:
+ if (a3 == 3 && pSourcePlayer && gFrameClock > pSourcePlayer->at312 && Chance(0x4000))
+ {
+ sfxPlay3DSound(pSourcePlayer->pSprite, gPlayerGibThingComments[Random(10)], 0, 2);
+ pSourcePlayer->at312 = gFrameClock+3600;
+ }
+ break;
+ case 413:
+ seqSpawn(28, 3, pSprite->extra, -1);
+ break;
+ case 407:
+ seqSpawn(12, 3, pSprite->extra, -1);
+ GibSprite(pSprite, GIBTYPE_6, NULL, NULL);
+ break;
+ case 410:
+ seqSpawn(15, 3, pSprite->extra, -1);
+ break;
+ case 411:
+ seqSpawn(21, 3, pSprite->extra, -1);
+ GibSprite(pSprite, GIBTYPE_4, NULL, NULL);
+ break;
+ case 412:
+ switch (pXSprite->data1)
+ {
+ case -1:
+ GibSprite(pSprite, GIBTYPE_14, NULL, NULL);
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 312, pSprite->sectnum);
+ actPostSprite(pSprite->index, 1024);
+ break;
+ case 0:
+ seqSpawn(25, 3, pSprite->extra, nTreeToGibClient);
+ sfxPlay3DSound(pSprite, 351, -1, 0);
+ break;
+ case 1:
+ seqSpawn(26, 3, pSprite->extra, nTreeToGibClient);
+ sfxPlay3DSound(pSprite, 351, -1, 0);
+ break;
+ }
+ break;
+ case 422:
+ if (seqGetStatus(3, nXSprite) < 0)
+ seqSpawn(19, 3, pSprite->extra, -1);
+ break;
+ }
+ }
+ break;
+ }
+ return a4>>4;
+}
+
+void actHitcodeToData(int a1, HITINFO *pHitInfo, int *a3, spritetype **a4, XSPRITE **a5, int *a6, walltype **a7, XWALL **a8, int *a9, sectortype **a10, XSECTOR **a11)
+{
+ dassert(pHitInfo != NULL);
+ int nSprite = -1;
+ spritetype *pSprite = NULL;
+ XSPRITE *pXSprite = NULL;
+ int nWall = -1;
+ walltype *pWall = NULL;
+ XWALL *pXWall = NULL;
+ int nSector = -1;
+ sectortype *pSector = NULL;
+ XSECTOR *pXSector = NULL;
+ switch (a1)
+ {
+ case 3:
+ case 5:
+ nSprite = pHitInfo->hitsprite;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ pSprite = &sprite[nSprite];
+ if (pSprite->extra > 0)
+ pXSprite = &xsprite[pSprite->extra];
+ break;
+ case 0:
+ case 4:
+ nWall = pHitInfo->hitwall;
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ pWall = &wall[nWall];
+ if (pWall->extra > 0)
+ pXWall = &xwall[pWall->extra];
+ break;
+ case 1:
+ case 2:
+ case 6:
+ nSector = pHitInfo->hitsect;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ pSector = §or[nSector];
+ if (pSector->extra > 0)
+ pXSector = &xsector[pSector->extra];
+ break;
+ }
+ if (a3)
+ *a3 = nSprite;
+ if (a4)
+ *a4 = pSprite;
+ if (a5)
+ *a5 = pXSprite;
+ if (a6)
+ *a6 = nWall;
+ if (a7)
+ *a7 = pWall;
+ if (a8)
+ *a8 = pXWall;
+ if (a9)
+ *a9 = nSector;
+ if (a10)
+ *a10 = pSector;
+ if (a11)
+ *a11 = pXSector;
+}
+
+void actImpactMissile(spritetype *pMissile, int a2)
+{
+ int nXMissile = pMissile->extra;
+ dassert(nXMissile > 0 && nXMissile < kMaxXSprites);
+ XSPRITE *pXMissile = &xsprite[pMissile->extra];
+ int nSpriteHit = -1;
+ spritetype *pSpriteHit = NULL;
+ XSPRITE *pXSpriteHit = NULL;
+ int nWallHit = -1;
+ walltype *pWallHit = NULL;
+ XWALL *pXWallHit = NULL;
+ int nSectorHit = -1;
+ sectortype *pSectorHit = NULL;
+ XSECTOR *pXSectorHit = NULL;
+ actHitcodeToData(a2, &gHitInfo, &nSpriteHit, &pSpriteHit, &pXSpriteHit, &nWallHit, &pWallHit, &pXWallHit, &nSectorHit, &pSectorHit, &pXSectorHit);
+ THINGINFO *pThingInfo = NULL;
+ DUDEINFO *pDudeInfo = NULL;
+ if (a2 == 3 && pSpriteHit)
+ {
+ if (pSpriteHit->statnum == 4)
+ {
+ dassert(pXSpriteHit != NULL);
+ pThingInfo = &thingInfo[pSpriteHit->type-kThingBase];
+ }
+ else if (pSpriteHit->statnum == 6)
+ {
+ dassert(pXSpriteHit != NULL);
+ pDudeInfo = &dudeInfo[pSpriteHit->type-kDudeBase];
+ }
+ }
+ switch (pMissile->type)
+ {
+ case 315:
+ if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ DAMAGE_TYPE rand1 = (DAMAGE_TYPE)Random(7);
+ int rand2 = (7+Random(7))<<4;
+ actDamageSprite(nOwner, pSpriteHit, rand1, rand2);
+ if ((pThingInfo && pThingInfo->at17[DAMAGE_TYPE_1] != 0) || (pDudeInfo && pDudeInfo->at70[DAMAGE_TYPE_1] != 0))
+ actBurnSprite(pMissile->owner, pXSpriteHit, 360);
+ }
+ if (pMissile->extra > 0)
+ {
+ actPostSprite(pMissile->index, 0);
+ if (pMissile->ang == 1024)
+ sfxPlay3DSound(pMissile, 307, -1, 0);
+ pMissile->type = 0;
+ seqSpawn(9, 3, pMissile->extra, -1);
+ }
+ else
+ {
+ actPostSprite(pMissile->index, 1024);
+ }
+ break;
+ case 302:
+ sub_51340(pMissile, a2);
+ if ((a2 == 0 || a2 == 4) && pWallHit)
+ {
+ spritetype *pFX = gFX.fxSpawn(FX_52, pMissile->sectnum, pMissile->x, pMissile->y, pMissile->z, 0);
+ if (pFX)
+ pFX->ang = (GetWallAngle(nWallHit)+512)&2047;
+ }
+ GibSprite(pMissile, GIBTYPE_24, NULL, NULL);
+ actPostSprite(pMissile->index, 1024);
+ break;
+ case 309:
+ seqKill(3, nXMissile);
+ if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (15+Random(7))<<4;
+ actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
+ }
+ actPostSprite(pMissile->index, 1024);
+ break;
+ case 311:
+ sfxKill3DSound(pMissile, -1, -1);
+ sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 306, pMissile->sectnum);
+ GibSprite(pMissile, GIBTYPE_6, NULL, NULL);
+ if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (25+Random(20))<<4;
+ actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_5, nDamage);
+ }
+ actPostSprite(pMissile->index, 1024);
+ break;
+ case 316:
+ case 317:
+ sfxKill3DSound(pMissile, -1, -1);
+ sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 306, pMissile->sectnum);
+ if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
+ {
+ if (pDudeInfo)
+ {
+ }
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDmgMul = 3;
+ if (pMissile->type == 317)
+ nDmgMul = 6;
+ int nDamage = (nDmgMul+Random(nDmgMul))<<4;
+ actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_5, nDamage);
+ }
+ actPostSprite(pMissile->index, 1024);
+ break;
+ case 305:
+ case 312:
+ if (a2 == 3 && pSpriteHit && (pThingInfo || pDudeInfo))
+ {
+ if (pThingInfo && pSpriteHit->type == 400 && pXSpriteHit->burnTime == 0)
+ evPost(nSpriteHit, 3, 0, CALLBACK_ID_0);
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (50+Random(50))<<4;
+ actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
+ }
+ actExplodeSprite(pMissile);
+ break;
+ case 303:
+ sfxKill3DSound(pMissile, -1, -1);
+ actExplodeSprite(pMissile);
+ break;
+ case 301:
+ sfxKill3DSound(pMissile, -1, -1);
+ if (a2 == 3 && pSpriteHit)
+ {
+ if (pThingInfo || pDudeInfo)
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ if ((pThingInfo && pThingInfo->at17[DAMAGE_TYPE_1] != 0) || (pDudeInfo && pDudeInfo->at70[DAMAGE_TYPE_1] != 0))
+ {
+ if (pThingInfo && pSpriteHit->type == 400 && pXSpriteHit->burnTime == 0)
+ evPost(nSpriteHit, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(pMissile->owner, pXSpriteHit, 480);
+ sub_2A620(nOwner, pMissile->x, pMissile->y, pMissile->z, pMissile->sectnum, 16, 20, 10, DAMAGE_TYPE_2, 6, 480, 0, 0);
+ }
+ else
+ {
+ int nDamage = (20+Random(10))<<4;
+ actDamageSprite(nOwner, pSpriteHit, DAMAGE_TYPE_2, nDamage);
+ }
+ if (surfType[pSpriteHit->picnum] == 4)
+ {
+ pMissile->picnum = 2123;
+ pXMissile->target = nSpriteHit;
+ pXMissile->targetZ = pMissile->z-pSpriteHit->z;
+ pXMissile->goalAng = getangle(pMissile->x-pSpriteHit->x, pMissile->y-pSpriteHit->y)-pSpriteHit->ang;
+ pXMissile->state = 1;
+ actPostSprite(pMissile->index, 14);
+ pMissile->cstat &= ~257;
+ break;
+ }
+ }
+ }
+ GibSprite(pMissile, GIBTYPE_17, NULL, NULL);
+ actPostSprite(pMissile->index, 1024);
+ break;
+ case 304:
+ case 308:
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ if (pObject->extra > 0)
+ {
+ XSPRITE *pXObject = &xsprite[pObject->extra];
+ if ((pObject->statnum == 4 || pObject->statnum == 6) && pXObject->burnTime == 0)
+ evPost(nObject, 3, 0, CALLBACK_ID_0);
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ actBurnSprite(pMissile->owner, pXObject, (4+gGameOptions.nDifficulty)<<2);
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_1, 8);
+ }
+ }
+ break;
+ case 313:
+ actExplodeSprite(pMissile);
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ if (pObject->extra > 0)
+ {
+ XSPRITE *pXObject = &xsprite[pObject->extra];
+ if ((pObject->statnum == 4 || pObject->statnum == 6) && pXObject->burnTime == 0)
+ evPost(nObject, 3, 0, CALLBACK_ID_0);
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ actBurnSprite(pMissile->owner, pXObject, (4+gGameOptions.nDifficulty)<<2);
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_1, 8);
+ int nDamage = (25+Random(10))<<4;
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_2, nDamage);
+ }
+ }
+ actExplodeSprite(pMissile);
+ break;
+ case 314:
+ actExplodeSprite(pMissile);
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ if (pObject->extra > 0)
+ {
+ XSPRITE *pXObject = &xsprite[pObject->extra];
+ if ((pObject->statnum == 4 || pObject->statnum == 6) && pXObject->burnTime == 0)
+ evPost(nObject, 3, 0, CALLBACK_ID_0);
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ actBurnSprite(pMissile->owner, pXObject, 32);
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_5, 12);
+ int nDamage = (25+Random(10))<<4;
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_2, nDamage);
+ }
+ }
+ actExplodeSprite(pMissile);
+ break;
+ case 307:
+ sfxKill3DSound(pMissile, -1, -1);
+ sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 522, pMissile->sectnum);
+ actPostSprite(pMissile->index, 15);
+ seqSpawn(20, 3, pMissile->extra, -1);
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ if (pObject->statnum == 6)
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (25+Random(10))<<4;
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_5, nDamage);
+ }
+ }
+ break;
+ case 300:
+ actPostSprite(pMissile->index, 15);
+ pMissile->cstat &= ~16;
+ pMissile->type = 0;
+ seqSpawn(20, 3, pMissile->extra, -1);
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ if (pObject->statnum == 6)
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (10+Random(10))<<4;
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_5, nDamage);
+ spritetype *pOwner = &sprite[nOwner];
+ XSPRITE *pXOwner = &xsprite[pOwner->extra];
+ int nType = pOwner->type-kDudeBase;
+ if (pXOwner->health > 0)
+ actHealDude(pXOwner, 10, dudeInfo[nType].startHealth);
+ }
+ }
+ break;
+ case 306:
+ sfxKill3DSound(pMissile, -1, -1);
+ sfxPlay3DSound(pMissile->x, pMissile->y, pMissile->z, 518, pMissile->sectnum);
+ GibSprite(pMissile, (a2 == 2) ? GIBTYPE_23 : GIBTYPE_22, NULL, NULL);
+ evKill(pMissile->index, 3);
+ seqKill(3, nXMissile);
+ actPostSprite(pMissile->index, 1024);
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (15+Random(10))<<4;
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_6, nDamage);
+ }
+ break;
+ case 310:
+ default:
+ seqKill(3, nXMissile);
+ actPostSprite(pMissile->index, 1024);
+ if (a2 == 3)
+ {
+ int nObject = gHitInfo.hitsprite;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ int nDamage = (10+Random(10))<<4;
+ actDamageSprite(nOwner, pObject, DAMAGE_TYPE_0, nDamage);
+ }
+ break;
+ }
+ pMissile->cstat &= ~257;
+}
+
+void actKickObject(spritetype *pSprite1, spritetype *pSprite2)
+{
+ int nSprite1 = pSprite1->index;
+ int nSprite2 = pSprite2->index;
+ int nSpeed = ClipLow(approxDist(xvel[nSprite1], yvel[nSprite1])*2, 0xaaaaa);
+ xvel[nSprite2] = mulscale30(nSpeed, Cos(pSprite1->ang+Random2(85)));
+ yvel[nSprite2] = mulscale30(nSpeed, Sin(pSprite1->ang+Random2(85)));
+ zvel[nSprite2] = mulscale(nSpeed, -0x2000, 14);
+ pSprite2->hitag = 7;
+}
+
+void actTouchFloor(spritetype *pSprite, int nSector)
+{
+ dassert(pSprite != NULL);
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype * pSector = §or[nSector];
+ XSECTOR * pXSector = NULL;
+ if (pSector->extra > 0)
+ pXSector = &xsector[pSector->extra];
+
+ if (pXSector && (pSector->lotag == 618 || pXSector->damageType > 0))
+ {
+ DAMAGE_TYPE nDamageType;
+ if (pSector->lotag == 618)
+ nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType, DAMAGE_TYPE_0, DAMAGE_TYPE_6);
+ else
+ nDamageType = (DAMAGE_TYPE)ClipRange(pXSector->damageType - 1, DAMAGE_TYPE_0, DAMAGE_TYPE_6);
+ int nDamage;
+ if (pXSector->data)
+ nDamage = ClipRange(pXSector->data, 0, 1000);
+ else
+ nDamage = 1000;
+ actDamageSprite(pSprite->index, pSprite, nDamageType, scale(4, nDamage, 120) << 4);
+ }
+ if (tileGetSurfType(nSector + 0x4000) == 14)
+ {
+ actDamageSprite(pSprite->index, pSprite, DAMAGE_TYPE_1, 16);
+ sfxPlay3DSound(pSprite, 352, 5, 2);
+ }
+}
+
+void ProcessTouchObjects(spritetype *pSprite, int nXSprite)
+{
+ int nSprite = pSprite->index;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ SPRITEHIT *pSpriteHit = &gSpriteHit[nXSprite];
+ PLAYER *pPlayer = NULL;
+ if (IsPlayerSprite(pSprite))
+ pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ int nHitSprite = pSpriteHit->ceilhit & 0x1fff;
+ switch (pSpriteHit->ceilhit&0xe000)
+ {
+ case 0x8000:
+ break;
+ case 0xc000:
+ if (sprite[nHitSprite].extra > 0)
+ {
+ spritetype *pSprite2 = &sprite[nHitSprite];
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ if ((pSprite2->statnum == 4 || pSprite2->statnum == 6) && (xvel[nSprite] != 0 || yvel[nSprite] != 0 || zvel[nSprite] != 0))
+ {
+ if (pSprite2->statnum == 4)
+ {
+ int nType = pSprite2->type-kThingBase;
+ THINGINFO *pThingInfo = &thingInfo[nType];
+ if (pThingInfo->at5&1)
+ pSprite2->hitag |= 1;
+ if (pThingInfo->at5&2)
+ pSprite2->hitag |= 4;
+ // Inlined ?
+ xvel[pSprite2->index] += mulscale(4, pSprite2->x-sprite[nSprite].x, 2);
+ yvel[pSprite2->index] += mulscale(4, pSprite2->y-sprite[nSprite].y, 2);
+ }
+ else
+ {
+ pSprite2->hitag |= 5;
+ xvel[pSprite2->index] += mulscale(4, pSprite2->x-sprite[nSprite].x, 2);
+ yvel[pSprite2->index] += mulscale(4, pSprite2->y-sprite[nSprite].y, 2);
+
+ // by NoOne: add size shroom abilities
+ if ((IsPlayerSprite(pSprite) && isShrinked(pSprite)) || (IsPlayerSprite(pSprite2) && isGrown(pSprite2))) {
+
+ int mass1 = dudeInfo[pSprite2->type - kDudeBase].mass;
+ int mass2 = dudeInfo[pSprite->type - kDudeBase].mass;
+ switch (pSprite->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ mass2 = getDudeMassBySpriteSize(pSprite);
+ break;
+ }
+ if (mass1 > mass2) {
+ int dmg = abs((mass1 - mass2) * (pSprite2->clipdist - pSprite->clipdist));
+ if (IsDudeSprite(pSprite2)) {
+ if (dmg > 0)
+ actDamageSprite(pSprite2->xvel, pSprite, (Chance(0x2000)) ? DAMAGE_TYPE_0 : (Chance(0x4000)) ? DAMAGE_TYPE_3 : DAMAGE_TYPE_2, dmg);
+
+ if (Chance(0x0200))
+ actKickObject(pSprite2, pSprite);
+ }
+ }
+ }
+
+ if (!IsPlayerSprite(pSprite) || gPlayer[pSprite->type - kDudePlayer1].at31a == 0) {
+ switch (pSprite2->type) {
+ case 229:
+ actDamageSprite(pSprite2->index, pSprite, DAMAGE_TYPE_3, pXSprite->health << 2);
+ break;
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ int dmg = (getDudeMassBySpriteSize(pSprite2) - getDudeMassBySpriteSize(pSprite)) + pSprite2->clipdist;
+ if (dmg > 0) {
+ if (IsPlayerSprite(pSprite) && powerupCheck(&gPlayer[pSprite->type - kDudePlayer1],15) > 0)
+ actDamageSprite(pSprite2->xvel, pSprite, DAMAGE_TYPE_3, dmg);
+ else
+ actDamageSprite(pSprite2->xvel, pSprite, DAMAGE_TYPE_0, dmg);
+ }
+
+ if (!IsPlayerSprite(pSprite) && pSprite2->extra >= 0 && !isActive(pSprite2->xvel))
+ aiActivateDude(pSprite2, &xsprite[pSprite2->extra]);
+ break;
+
+ }
+
+ }
+ }
+ }
+ if (pSprite2->type == 454)
+ {
+ if (pXSprite2->state)
+ {
+ pXSprite2->data1 = 1;
+ pXSprite2->data2 = ClipHigh(pXSprite2->data2+8, 600);
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 16);
+ }
+ else
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 1);
+ }
+ }
+ break;
+ }
+ nHitSprite = pSpriteHit->hit & 0x1fff;
+ switch (pSpriteHit->hit&0xe000)
+ {
+ case 0x8000:
+ break;
+ case 0xc000:
+ if (sprite[nHitSprite].extra > 0)
+ {
+ spritetype *pSprite2 = &sprite[nHitSprite];
+ //XSPRITE *pXSprite2 = &Xsprite[pSprite2->extra];
+
+ // by NoOne: add size shroom abilities
+ if ((IsPlayerSprite(pSprite2) && isShrinked(pSprite2)) || (IsPlayerSprite(pSprite) && isGrown(pSprite))) {
+ if (xvel[pSprite->xvel] != 0 && IsDudeSprite(pSprite2)) {
+ int mass1 = dudeInfo[pSprite->type - kDudeBase].mass;
+ int mass2 = dudeInfo[pSprite2->type - kDudeBase].mass;
+ switch (pSprite2->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ mass2 = getDudeMassBySpriteSize(pSprite2);
+ break;
+ }
+ if (mass1 > mass2) {
+ actKickObject(pSprite, pSprite2);
+ sfxPlay3DSound(pSprite, 357, -1, 1);
+ int dmg = (mass1 - mass2) + abs(xvel[pSprite->xvel] >> 16);
+ if (dmg > 0)
+ actDamageSprite(nSprite, pSprite2, (Chance(0x2000)) ? DAMAGE_TYPE_0 : DAMAGE_TYPE_2, dmg);
+ }
+ }
+ }
+
+ switch (pSprite->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ {
+ if (IsDudeSprite(pSprite2) && !IsPlayerSprite(pSprite2)) {
+ int mass1 = getDudeMassBySpriteSize(pSprite);
+ int mass2 = getDudeMassBySpriteSize(pSprite2);
+
+ if (mass1 > mass2) {
+ if ((pXSprite->target == pSprite2->xvel && !dudeIsMelee(pXSprite) && Chance(0x0500)) || pXSprite->target != pSprite2->xvel)
+ actKickObject(pSprite, pSprite2);
+ if (pSprite2->extra >= 0 && !isActive(pSprite2->xvel))
+ aiActivateDude(pSprite2, &xsprite[pSprite2->extra]);
+ }
+ }
+ break;
+ }
+ }
+
+ switch (pSprite2->type)
+ {
+ case 415:
+ actKickObject(pSprite, pSprite2);
+ break;
+ case 427:
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
+ actKickObject(pSprite, pSprite2);
+ actDamageSprite(-1, pSprite2, DAMAGE_TYPE_0, 80);
+ break;
+ case 239:
+ case 240:
+ case 241:
+ case 242:
+ // This does not make sense
+ pXSprite->burnTime = ClipLow(pXSprite->burnTime-4, 0);
+ actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
+ break;
+ }
+ }
+ break;
+ }
+ nHitSprite = pSpriteHit->florhit & 0x1fff;
+ switch (pSpriteHit->florhit&0xe000)
+ {
+ case 0x8000:
+ break;
+ case 0x4000:
+ actTouchFloor(pSprite, nHitSprite);
+ break;
+ case 0xc000:
+ if (sprite[nHitSprite].extra > 0)
+ {
+ spritetype *pSprite2 = &sprite[nHitSprite];
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+
+ // by NoOne: add size shroom abilities
+ if ((IsPlayerSprite(pSprite2) && isShrinked(pSprite2)) || (IsPlayerSprite(pSprite) && isGrown(pSprite))) {
+
+ int mass1 = dudeInfo[pSprite->type - kDudeBase].mass;
+ int mass2 = dudeInfo[pSprite2->type - kDudeBase].mass;
+ switch (pSprite2->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ mass2 = getDudeMassBySpriteSize(pSprite2);
+ break;
+ }
+ if (mass1 > mass2 && IsDudeSprite(pSprite2)) {
+ if ((IsPlayerSprite(pSprite2) && Chance(0x500)) || !IsPlayerSprite(pSprite2))
+ actKickObject(pSprite, pSprite2);
+
+ int dmg = (mass1 - mass2) + pSprite->clipdist;
+ if (dmg > 0)
+ actDamageSprite(nSprite, pSprite2, (Chance(0x2000)) ? DAMAGE_TYPE_0 : DAMAGE_TYPE_2, dmg);
+ }
+ }
+
+ switch (pSprite->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ {
+ if (IsDudeSprite(pSprite2) && !IsPlayerSprite(pSprite2)) {
+ int mass1 = getDudeMassBySpriteSize(pSprite);
+ int mass2 = getDudeMassBySpriteSize(pSprite2);
+
+ if (mass1 > mass2) {
+ if (Chance((pXSprite->target == pSprite2->xvel) ? 0x1000 : 0x2000)) actKickObject(pSprite, pSprite2);
+ if (pSprite2->extra >= 0 && !isActive(pSprite2->xvel))
+ aiActivateDude(pSprite2, &xsprite[pSprite2->extra]);
+ }
+ }
+ break;
+ }
+ }
+
+
+ switch (pSprite2->type)
+ {
+ case 415:
+ if (pPlayer)
+ {
+ if (pPlayer->at30e > gFrameClock)
+ return;
+ pPlayer->at30e = gFrameClock+60;
+ }
+ actKickObject(pSprite, pSprite2);
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
+ sfxPlay3DSound(pSprite, 374, 0, 0);
+ break;
+ case 427:
+ if (pPlayer)
+ {
+ if (pPlayer->at30e > gFrameClock)
+ return;
+ pPlayer->at30e = gFrameClock+60;
+ }
+ actKickObject(pSprite, pSprite2);
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, 357, pSprite->sectnum);
+ actDamageSprite(-1, pSprite2, DAMAGE_TYPE_0, 80);
+ break;
+ case 454:
+ if (pXSprite2->state)
+ {
+ pXSprite2->data1 = 1;
+ pXSprite2->data2 = ClipHigh(pXSprite2->data2+8, 600);
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 16);
+ }
+ else
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 1);
+ break;
+ case 201:
+ case 202:
+ case 203:
+ case 204:
+ case 205:
+ case 206:
+ case 207:
+ case 210:
+ case 211:
+ case 212:
+ case 213:
+ case 214:
+ case 215:
+ case 217:
+ case 219:
+ case 220:
+ case 221:
+ case 222:
+ case 223:
+ case 224:
+ case 225:
+ case 226:
+ case 227:
+ case 228:
+ case 229:
+ case 231:
+ case 232:
+ case 233:
+ case 234:
+ case 235:
+ case 236:
+ case 237:
+ case 238:
+ if (pPlayer && !isShrinked(pSprite))
+ actDamageSprite(nSprite, pSprite2,DAMAGE_TYPE_2, 8);
+ break;
+ }
+ }
+ break;
+ }
+}
+
+void actAirDrag(spritetype *pSprite, int a2)
+{
+ int vbp = 0;
+ int v4 = 0;
+ int nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ if (nXSector > 0)
+ {
+ dassert(nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (pXSector->windVel && (pXSector->windAlways || pXSector->busy))
+ {
+ int vcx = pXSector->windVel<<12;
+ if (!pXSector->windAlways && pXSector->busy)
+ vcx = mulscale16(vcx, pXSector->busy);
+ vbp = mulscale30(vcx, Cos(pXSector->windAng));
+ v4 = mulscale30(vcx, Sin(pXSector->windAng));
+ }
+ }
+ xvel[pSprite->index] += mulscale16(vbp-xvel[pSprite->index], a2);
+ yvel[pSprite->index] += mulscale16(v4-yvel[pSprite->index], a2);
+ zvel[pSprite->index] -= mulscale16(zvel[pSprite->index], a2);
+}
+
+int MoveThing(spritetype *pSprite)
+{
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pSprite->index;
+ int v8 = 0;
+ dassert(pSprite->type >= kThingBase && pSprite->type < kThingMax);
+ THINGINFO *pThingInfo = &thingInfo[pSprite->type-kThingBase];
+ int nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (xvel[nSprite] || yvel[nSprite])
+ {
+ short bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~257;
+ v8 = gSpriteHit[nXSprite].hit = ClipMove((int*)&pSprite->x, (int*)&pSprite->y, (int*)&pSprite->z, &nSector, xvel[nSprite]>>12, yvel[nSprite]>>12, pSprite->clipdist<<2, (pSprite->z-top)/4, (bottom-pSprite->z)/4, CLIPMASK0);
+ pSprite->cstat = bakCstat;
+ dassert(nSector >= 0);
+ if (pSprite->sectnum != nSector)
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ ChangeSpriteSect(nSprite, nSector);
+ }
+ if ((gSpriteHit[nXSprite].hit&0xe000) == 0x8000)
+ {
+ int nHitWall = gSpriteHit[nXSprite].hit&0x1fff;
+ actWallBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], nHitWall, pThingInfo->at7);
+ switch (pSprite->type)
+ {
+ case 427:
+ sfxPlay3DSound(pSprite, 607, 0, 0);
+ actDamageSprite(-1, pSprite, DAMAGE_TYPE_0, 80);
+ break;
+ case 415:
+ sfxPlay3DSound(pSprite, 374, 0, 0);
+ break;
+ }
+ }
+ }
+ else
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
+ }
+ if (zvel[nSprite])
+ pSprite->z += zvel[nSprite]>>8;
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0);
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if ((pSprite->hitag & 2) && bottom < floorZ)
+ {
+ pSprite->z += 455;
+ zvel[nSprite] += 58254;
+ if (pSprite->type == 427)
+ {
+ spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ int v34 = (gFrameClock*3)&2047;
+ int v30 = (gFrameClock*5)&2047;
+ int vbx = (gFrameClock*11)&2047;
+ int v2c = 0x44444;
+ int v28 = 0;
+ int v24 = 0;
+ RotateVector(&v2c,&v28,vbx);
+ RotateVector(&v2c,&v24,v30);
+ RotateVector(&v28,&v24,v34);
+ xvel[pFX->index] = xvel[pSprite->index]+v2c;
+ yvel[pFX->index] = yvel[pSprite->index]+v28;
+ zvel[pFX->index] = zvel[pSprite->index]+v24;
+ }
+ }
+ }
+ if (CheckLink(pSprite))
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0);
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (bottom >= floorZ)
+ {
+ actTouchFloor(pSprite, pSprite->sectnum);
+ gSpriteHit[nXSprite].florhit = floorHit;
+ pSprite->z += floorZ-bottom;
+ int v20 = zvel[nSprite]-velFloor[pSprite->sectnum];
+ if (v20 > 0)
+ {
+ pSprite->hitag |= 4;
+ int vax = actFloorBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], (int*)&v20, pSprite->sectnum, pThingInfo->at7);
+ int nDamage = mulscale(vax, vax, 30)-pThingInfo->atb;
+ if (nDamage > 0)
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, nDamage);
+ zvel[nSprite] = v20;
+ if (velFloor[pSprite->sectnum] == 0 && klabs(zvel[nSprite]) < 0x10000)
+ {
+ zvel[nSprite] = 0;
+ pSprite->hitag &= ~4;
+ }
+ switch (pSprite->type)
+ {
+ case 428:
+ if (zvel[nSprite] == 0 || Chance(0xA000))
+ sub_2AA94(pSprite, pXSprite);
+ break;
+ case 427:
+ if (klabs(zvel[nSprite]) > 0x80000)
+ {
+ sfxPlay3DSound(pSprite, 607, 0, 0);
+ actDamageSprite(-1, pSprite, DAMAGE_TYPE_0, 80);
+ }
+ break;
+ case 415:
+ if (klabs(zvel[nSprite]) > 0x80000)
+ sfxPlay3DSound(pSprite, 374, 0, 0);
+ break;
+ }
+ v8 = 0x4000|nSector;
+ }
+ else if (zvel[nSprite] == 0)
+ pSprite->hitag &= ~4;
+ }
+ else
+ {
+ gSpriteHit[nXSprite].florhit = 0;
+ if (pSprite->hitag&2)
+ pSprite->hitag |= 4;
+ }
+ if (top <= ceilZ)
+ {
+ gSpriteHit[nXSprite].ceilhit = ceilHit;
+ pSprite->z += ClipLow(ceilZ-top, 0);
+ if (zvel[nSprite] < 0)
+ {
+ xvel[nSprite] = mulscale16(xvel[nSprite], 0xc000);
+ yvel[nSprite] = mulscale16(yvel[nSprite], 0xc000);
+ zvel[nSprite] = mulscale16(-zvel[nSprite], 0x4000);
+ switch (pSprite->type)
+ {
+ case 427:
+ if (klabs(zvel[nSprite]) > 0x80000)
+ {
+ sfxPlay3DSound(pSprite, 607, 0, 0);
+ actDamageSprite(-1, pSprite, DAMAGE_TYPE_0, 80);
+ }
+ break;
+ case 415:
+ if (klabs(zvel[nSprite]) > 0x80000)
+ sfxPlay3DSound(pSprite, 374, 0, 0);
+ break;
+ }
+ }
+ }
+ else
+ gSpriteHit[nXSprite].ceilhit = 0;
+ if (bottom >= floorZ)
+ {
+ int nVel = approxDist(xvel[nSprite], yvel[nSprite]);
+ int nVelClipped = ClipHigh(nVel, 0x11111);
+ if ((floorHit & 0xe000) == 0xc000)
+ {
+ int nHitSprite = floorHit & 0x1fff;
+ if ((sprite[nHitSprite].cstat & 0x30) == 0)
+ {
+ xvel[nSprite] += mulscale(4, pSprite->x - sprite[nHitSprite].x, 2);
+ yvel[nSprite] += mulscale(4, pSprite->y - sprite[nHitSprite].y, 2);
+ v8 = gSpriteHit[nXSprite].hit;
+ }
+ }
+ if (nVel > 0)
+ {
+ int t = divscale16(nVelClipped, nVel);
+ xvel[nSprite] -= mulscale16(t, xvel[nSprite]);
+ yvel[nSprite] -= mulscale16(t, yvel[nSprite]);
+ }
+ }
+ if (xvel[nSprite] || yvel[nSprite])
+ pSprite->ang = getangle(xvel[nSprite], yvel[nSprite]);
+ return v8;
+}
+
+void MoveDude(spritetype *pSprite)
+{
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pSprite->index;
+ PLAYER *pPlayer = NULL;
+ if (IsPlayerSprite(pSprite))
+ pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int bz = (bottom-pSprite->z)/4;
+ int tz = (pSprite->z-top)/4;
+ int wd = pSprite->clipdist<<2;
+ int nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ if (xvel[nSprite] || yvel[nSprite])
+ {
+ if (pPlayer && gNoClip)
+ {
+ pSprite->x += xvel[nSprite]>>12;
+ pSprite->y += yvel[nSprite]>>12;
+ if (!FindSector(pSprite->x, pSprite->y, &nSector))
+ nSector = pSprite->sectnum;
+ }
+ else
+ {
+ short bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~257;
+ gSpriteHit[nXSprite].hit = ClipMove((int*)&pSprite->x, (int*)&pSprite->y, (int*)&pSprite->z, &nSector, xvel[nSprite]>>12, yvel[nSprite]>>12, wd, tz, bz, 0x13001);
+ if (nSector == -1)
+ {
+ nSector = pSprite->sectnum;
+ if (pSprite->statnum == 6 || pSprite->statnum == 4)
+ actDamageSprite(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000<<4);
+ }
+ if (sector[nSector].lotag >= 612 && sector[nSector].lotag <= 617)
+ {
+ short nSector2 = nSector;
+ if (pushmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector2, wd, tz, bz, CLIPMASK0) == -1)
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, 1000 << 4);
+ if (nSector2 != -1)
+ nSector = nSector2;
+ }
+ dassert(nSector >= 0);
+ pSprite->cstat = bakCstat;
+ }
+ switch (gSpriteHit[nXSprite].hit&0xe000)
+ {
+ case 0xc000:
+ {
+ int nHitSprite = gSpriteHit[nXSprite].hit&0x1fff;
+ spritetype *pHitSprite = &sprite[nHitSprite];
+ XSPRITE *pHitXSprite = NULL;
+ // Should be pHitSprite here
+ if (pSprite->extra > 0)
+ pHitXSprite = &xsprite[pHitSprite->extra];
+ int nOwner = actSpriteOwnerToSpriteId(pHitSprite);
+ if (pHitSprite->statnum == 5 && !(pHitSprite->hitag&32) && pSprite->index != nOwner)
+ {
+ HITINFO hitInfo = gHitInfo;
+ gHitInfo.hitsprite = nSprite;
+ actImpactMissile(pHitSprite, 3);
+ gHitInfo = hitInfo;
+ }
+ // by NoOne: this is why touch for things never worked; they always ON
+ if (pHitXSprite && pHitXSprite->Touch /*&& !pHitXSprite->state*/ && !pHitXSprite->isTriggered) {
+ if (!pHitXSprite->DudeLockout || IsPlayerSprite(pSprite)) // allow dudeLockout for Touch flag
+ trTriggerSprite(nHitSprite, pHitXSprite, 33);
+ } if (pDudeInfo->lockOut && pHitXSprite && pHitXSprite->Push && !pHitXSprite->key && !pHitXSprite->DudeLockout && !pHitXSprite->state && !pHitXSprite->busy && !pPlayer)
+ trTriggerSprite(nHitSprite, pHitXSprite, 30);
+ break;
+ }
+ case 0x8000:
+ {
+ int nHitWall = gSpriteHit[nXSprite].hit&0x1fff;
+ walltype *pHitWall = &wall[nHitWall];
+ XWALL *pHitXWall = NULL;
+ if (pHitWall->extra > 0)
+ pHitXWall = &xwall[pHitWall->extra];
+ if (pDudeInfo->lockOut && pHitXWall && pHitXWall->triggerPush && !pHitXWall->key && !pHitXWall->dudeLockout && !pHitXWall->state && !pHitXWall->busy && !pPlayer)
+ trTriggerWall(nHitWall, pHitXWall, 50);
+ if (pHitWall->nextsector != -1)
+ {
+ sectortype *pHitSector = §or[pHitWall->nextsector];
+ XSECTOR *pHitXSector = NULL;
+ if (pHitSector->extra > 0)
+ pHitXSector = &xsector[pHitSector->extra];
+ if (pDudeInfo->lockOut && pHitXSector && pHitXSector->Wallpush && !pHitXSector->Key && !pHitXSector->at37_7 && !pHitXSector->state && !pHitXSector->busy && !pPlayer)
+ trTriggerSector(pHitWall->nextsector, pHitXSector, 40);
+ if (top < pHitSector->ceilingz || bottom > pHitSector->floorz)
+ {
+ // ???
+ }
+ }
+ actWallBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], nHitWall, 0);
+ break;
+ }
+ }
+ }
+ else
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector);
+ }
+ if (pSprite->sectnum != nSector)
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Exit && (pPlayer || !pXSector->at37_7))
+ trTriggerSector(pSprite->sectnum, pXSector, 43);
+ ChangeSpriteSect(nSprite, nSector);
+ nXSector = sector[nSector].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Enter && (pPlayer || !pXSector->at37_7))
+ {
+ if (sector[nSector].lotag == 604)
+ pXSector->data = pPlayer ? nSprite : -1;
+ trTriggerSector(nSector, pXSector, 42);
+ }
+ nSector = pSprite->sectnum;
+ }
+ char bUnderwater = 0;
+ char bDepth = 0;
+ if (sector[nSector].extra > 0)
+ {
+ XSECTOR *pXSector = &xsector[sector[nSector].extra];
+ if (pXSector->Underwater)
+ bUnderwater = 1;
+ if (pXSector->Depth)
+ bDepth = 1;
+ }
+ int nUpperLink = gUpperLink[nSector];
+ int nLowerLink = gLowerLink[nSector];
+ if (nUpperLink >= 0 && (sprite[nUpperLink].type == 9 || sprite[nUpperLink].type == 13))
+ bDepth = 1;
+ if (nLowerLink >= 0 && (sprite[nLowerLink].type == 10 || sprite[nLowerLink].type == 14))
+ bDepth = 1;
+ if (pPlayer)
+ wd += 16;
+ if (zvel[nSprite])
+ pSprite->z += zvel[nSprite]>>8;
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, 0x13001);
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (pSprite->hitag & 2)
+ {
+ int vc = 58254;
+ if (bDepth)
+ {
+ if (bUnderwater)
+ {
+ int cz = getceilzofslope(nSector, pSprite->x, pSprite->y);
+ if (cz > top)
+ vc += ((bottom-cz)*-80099) / (bottom-top);
+ else
+ vc = 0;
+ }
+ else
+ {
+ int fz = getflorzofslope(nSector, pSprite->x, pSprite->y);
+ if (fz < bottom)
+ vc += ((bottom-fz)*-80099) / (bottom-top);
+ }
+ }
+ else
+ {
+ if (bUnderwater)
+ vc = 0;
+ else if (bottom >= floorZ)
+ vc = 0;
+ }
+ if (vc)
+ {
+ pSprite->z += ((vc*4)/2)>>8;
+ zvel[nSprite] += vc;
+ }
+ }
+ if (pPlayer && zvel[nSprite] > 0x155555 && !pPlayer->at31b && pXSprite->height > 0)
+ {
+ pPlayer->at31b = 1;
+ sfxPlay3DSound(pSprite, 719, 0, 0);
+ }
+ int nLink = CheckLink(pSprite);
+ if (nLink)
+ {
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, 0x13001);
+ if (pPlayer)
+ playerResetInertia(pPlayer);
+ switch (nLink)
+ {
+ case 12:
+ if (pPlayer == gView)
+ SetBitString(gotpic, sector[pSprite->sectnum].floorpicnum);
+ break;
+ case 11:
+ if (pPlayer == gView)
+ SetBitString(gotpic, sector[pSprite->sectnum].ceilingpicnum);
+ break;
+ case 10:
+ case 14:
+ pXSprite->medium = 0;
+ if (pPlayer)
+ {
+ pPlayer->at2f = 0;
+ pPlayer->at302 = 0;
+ if (!pPlayer->at31c && pPlayer->atc.buttonFlags.jump)
+ {
+ zvel[nSprite] = -0x6aaaa;
+ pPlayer->at31c = 1;
+ }
+ sfxPlay3DSound(pSprite, 721, -1, 0);
+ }
+ else
+ {
+ switch (pSprite->type)
+ {
+ case 201:
+ case 202:
+ aiNewState(pSprite, pXSprite, &cultistGoto);
+ break;
+ case 217:
+ aiNewState(pSprite, pXSprite, &gillBeastGoto);
+ pSprite->hitag |= 6;
+ break;
+ case 218:
+ actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000<<4);
+ break;
+ }
+ }
+ break;
+ // By NoOne: part of "change of global view palette for stacks" feature
+ case kMarkerUpWater:
+ case kMarkerUpGoo:
+ {
+ pXSprite->medium = nLink == kMarkerUpGoo ? 2 : 1;
+
+ if (pPlayer)
+ {
+ // look for palette in data2 of marker. If value <= 0, use default ones.
+ pPlayer->nWaterPal = 0;
+ int nXUpper = sprite[gUpperLink[nSector]].extra;
+ if (nXUpper >= 0)
+ pPlayer->nWaterPal = xsprite[nXUpper].data2;
+
+ pPlayer->at2f = 1;
+ pXSprite->burnTime = 0;
+ pPlayer->at302 = klabs(zvel[nSprite]) >> 12;
+ evPost(nSprite, 3, 0, CALLBACK_ID_10);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ }
+ else
+ {
+ switch (pSprite->type)
+ {
+ case 201:
+ case 202:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ break;
+ case 240:
+ {
+ // There is no difference between water and goo except following chance:
+ if (Chance(nLink == kMarkerUpGoo ? 0x400 : 0xa00))
+ {
+ pSprite->type = 201;
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ }
+ else
+ {
+ pSprite->type = 202;
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ }
+ break;
+ }
+ case 203:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ break;
+ case 204:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &zombieFGoto);
+ break;
+ case 217:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &gillBeastSwimGoto);
+ pSprite->hitag &= ~6;
+ break;
+ case 206:
+ case 211:
+ case 213:
+ case 214:
+ case 215:
+ case 219:
+ case 220:
+ case 239:
+ actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000 << 4);
+ break;
+ }
+ }
+ break;
+ }
+ /*case 13:
+ pXSprite->medium = 2;
+ if (pPlayer)
+ {
+ pPlayer->changeTargetKin = 1;
+ pXSprite->burnTime = 0;
+ pPlayer->at302 = klabs(zvel[nSprite])>>12;
+ evPost(nSprite, 3, 0, CALLBACK_ID_10);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ }
+ else
+ {
+ switch (pSprite->type)
+ {
+ case 201:
+ case 202:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ break;
+ case 240:
+ if (Chance(0x400))
+ {
+ pSprite->type = 201;
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ }
+ else
+ {
+ pSprite->type = 202;
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ }
+ break;
+ case 203:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ break;
+ case 204:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &zombieFGoto);
+ break;
+ case 217:
+ pXSprite->burnTime = 0;
+ evPost(nSprite, 3, 0, CALLBACK_ID_11);
+ sfxPlay3DSound(pSprite, 720, -1, 0);
+ aiNewState(pSprite, pXSprite, &gillBeastSwimGoto);
+ pSprite->hitag &= ~6;
+ break;
+ case 206:
+ case 211:
+ case 213:
+ case 214:
+ case 215:
+ case 219:
+ case 220:
+ case 239:
+ actKillDude(pSprite->index, pSprite, DAMAGE_TYPE_0, 1000<<4);
+ break;
+ }
+ }
+ break;*/
+ }
+ }
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (pPlayer && bottom >= floorZ)
+ {
+ int floorZ2 = floorZ;
+ int floorHit2 = floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, 0x13001);
+ if (bottom <= floorZ && pSprite->z - floorZ2 < bz)
+ {
+ floorZ = floorZ2;
+ floorHit = floorHit2;
+ }
+ }
+ if (floorZ <= bottom)
+ {
+ gSpriteHit[nXSprite].florhit = floorHit;
+ pSprite->z += floorZ-bottom;
+ int v30 = zvel[nSprite]-velFloor[pSprite->sectnum];
+ if (v30 > 0)
+ {
+ int vax = actFloorBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], (int*)&v30, pSprite->sectnum, 0);
+ int nDamage = mulscale(vax, vax, 30);
+ if (pPlayer)
+ {
+ pPlayer->at31b = 0;
+ if (nDamage > (15<<4) && (pSprite->hitag&4))
+ playerLandingSound(pPlayer);
+ if (nDamage > (30<<4))
+ sfxPlay3DSound(pSprite, 701, 0, 0);
+ }
+ nDamage -= 100<<4;
+ if (nDamage > 0)
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, nDamage);
+ zvel[nSprite] = v30;
+ if (klabs(zvel[nSprite]) < 0x10000)
+ {
+ zvel[nSprite] = velFloor[pSprite->sectnum];
+ pSprite->hitag &= ~4;
+ }
+ else
+ pSprite->hitag |= 4;
+ switch (tileGetSurfType(floorHit))
+ {
+ case 5:
+ gFX.fxSpawn(FX_9, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
+ break;
+ case 14:
+ {
+ spritetype *pFX = gFX.fxSpawn(FX_10, pSprite->sectnum, pSprite->x, pSprite->y, floorZ, 0);
+ if (pFX)
+ {
+ for (int i = 0; i < 7; i++)
+ {
+ spritetype *pFX2 = gFX.fxSpawn(FX_14, pFX->sectnum, pFX->x, pFX->y, pFX->z, 0);
+ if (pFX2)
+ {
+ xvel[pFX2->index] = Random2(0x6aaaa);
+ yvel[pFX2->index] = Random2(0x6aaaa);
+ zvel[pFX2->index] = -Random(0xd5555);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ else if (zvel[nSprite] == 0)
+ pSprite->hitag &= ~4;
+ }
+ else
+ {
+ gSpriteHit[nXSprite].florhit = 0;
+ if (pSprite->hitag&2)
+ pSprite->hitag |= 4;
+ }
+ if (top <= ceilZ)
+ {
+ gSpriteHit[nXSprite].ceilhit = ceilHit;
+ pSprite->z += ClipLow(ceilZ-top, 0);
+ if (zvel[nSprite] <= 0 && (pSprite->hitag&4))
+ zvel[nSprite] = mulscale16(-zvel[nSprite], 0x2000);
+ }
+ else
+ gSpriteHit[nXSprite].ceilhit = 0;
+ GetSpriteExtents(pSprite,&top,&bottom);
+
+ pXSprite->height = ClipLow(floorZ-bottom, 0)>>8;
+ if (xvel[nSprite] || yvel[nSprite])
+ {
+ if ((floorHit & 0xe000) == 0xc000)
+ {
+ int nHitSprite = floorHit & 0x1fff;
+ if ((sprite[nHitSprite].cstat & 0x30) == 0)
+ {
+ xvel[nSprite] += mulscale(4, pSprite->x - sprite[nHitSprite].x, 2);
+ yvel[nSprite] += mulscale(4, pSprite->y - sprite[nHitSprite].y, 2);
+ return;
+ }
+ }
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0 && xsector[nXSector].Underwater)
+ return;
+ if (pXSprite->height >= 0x100)
+ return;
+ int nDrag = gDudeDrag;
+ if (pXSprite->height > 0)
+ nDrag -= scale(gDudeDrag, pXSprite->height, 0x100);
+ xvel[nSprite] -= mulscale16r(xvel[nSprite], nDrag);
+ yvel[nSprite] -= mulscale16r(yvel[nSprite], nDrag);
+
+ if (approxDist(xvel[nSprite], yvel[nSprite]) < 0x1000)
+ xvel[nSprite] = yvel[nSprite] = 0;
+ }
+}
+
+int MoveMissile(spritetype *pSprite)
+{
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int vdi = -1;
+ spritetype *pOwner = NULL;
+ int bakCstat = 0;
+ if (pSprite->owner >= 0)
+ {
+ int nOwner = actSpriteOwnerToSpriteId(pSprite);
+ pOwner = &sprite[nOwner];
+ if (IsDudeSprite(pOwner))
+ {
+ bakCstat = pOwner->cstat;
+ pOwner->cstat &= ~257;
+ }
+ else
+ pOwner = NULL;
+ }
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ if (pSprite->type == 304)
+ actAirDrag(pSprite, 0x1000);
+ int nSprite = pSprite->index;
+ if (pXSprite->target != -1 && (xvel[nSprite] || yvel[nSprite] || zvel[nSprite]))
+ {
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget;
+ if (pTarget->extra > 0)
+ pXTarget = &xsprite[pTarget->extra];
+ else
+ pXTarget = NULL;
+ if (pTarget->statnum == 6 && pXTarget && pXTarget->health > 0)
+ {
+ int nTargetAngle = getangle(-(pTarget->y-pSprite->y), pTarget->x-pSprite->x);
+ int UNUSED(nAngle) = getangle(xvel[nSprite]>>12,yvel[nSprite]>>12);
+ int vx = missileInfo[pSprite->type-300].at2;
+ int vy = 0;
+ RotatePoint(&vx, &vy, (nTargetAngle+1536)&2047, 0, 0);
+ xvel[nSprite] = vx;
+ yvel[nSprite] = vy;
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ int dz = pTarget->z-pSprite->z;
+ // Inlined
+ int vax = dz/10;
+ if (pTarget->z < pSprite->z)
+ vax = -vax;
+ zvel[nSprite] += vax;
+ ksqrt(dx*dx+dy*dy+dz*dz);
+ }
+ }
+ int vx = xvel[nSprite]>>12;
+ int vy = yvel[nSprite]>>12;
+ int vz = zvel[nSprite]>>8;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int i = 1;
+ while (1)
+ {
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int nSector2 = pSprite->sectnum;
+ clipmoveboxtracenum = 1;
+ int vdx = ClipMove(&x, &y, &z, &nSector2, vx, vy, pSprite->clipdist<<2, (z-top)/4, (bottom-z)/4, CLIPMASK0);
+ clipmoveboxtracenum = 3;
+ short nSector = nSector2;
+ if (nSector2 < 0)
+ {
+ vdi = -1;
+ break;
+ }
+ if (vdx)
+ {
+ int nHitSprite = vdx & 0x1fff;
+ if ((vdx&0xe000) == 0xc000)
+ {
+ gHitInfo.hitsprite = nHitSprite;
+ vdi = 3;
+ }
+ else if ((vdx & 0xe000) == 0x8000)
+ {
+ gHitInfo.hitwall = nHitSprite;
+ if (wall[nHitSprite].nextsector == -1)
+ vdi = 0;
+ else
+ {
+ int32_t fz, cz;
+ getzsofslope(wall[nHitSprite].nextsector, x, y, &cz, &fz);
+ if (z <= cz || z >= fz)
+ vdi = 0;
+ else
+ vdi = 4;
+ }
+ }
+ }
+ if (vdi == 4)
+ {
+ walltype *pWall = &wall[gHitInfo.hitwall];
+ if (pWall->extra > 0)
+ {
+ XWALL *pXWall = &xwall[pWall->extra];
+ if (pXWall->triggerVector)
+ {
+ trTriggerWall(gHitInfo.hitwall, pXWall, 51);
+ if (!(pWall->cstat&64))
+ {
+ vdi = -1;
+ if (i-- > 0)
+ continue;
+ vdi = 0;
+ break;
+ }
+ }
+ }
+ }
+ if (vdi >= 0 && vdi != 3)
+ {
+ int nAngle = getangle(xvel[nSprite], yvel[nSprite]);
+ x -= mulscale30(Cos(nAngle), 16);
+ y -= mulscale30(Sin(nAngle), 16);
+ int nVel = approxDist(xvel[nSprite], yvel[nSprite]);
+ vz -= scale(0x100, zvel[nSprite], nVel);
+ updatesector(x, y, &nSector);
+ nSector2 = nSector;
+ }
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRangeAtXYZ(x, y, z, nSector2, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, CLIPMASK0);
+ GetSpriteExtents(pSprite, &top, &bottom);
+ top += vz;
+ bottom += vz;
+ if (bottom >= floorZ)
+ {
+ gSpriteHit[nXSprite].florhit = floorHit;
+ vz += floorZ-bottom;
+ vdi = 2;
+ }
+ if (top <= ceilZ)
+ {
+ gSpriteHit[nXSprite].ceilhit = ceilHit;
+ vz += ClipLow(ceilZ-top, 0);
+ vdi = 1;
+ }
+ pSprite->x = x;
+ pSprite->y = y;
+ pSprite->z = z+vz;
+ updatesector(x, y, &nSector);
+ if (nSector >= 0 && nSector != pSprite->sectnum)
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ ChangeSpriteSect(nSprite, nSector);
+ }
+ CheckLink(pSprite);
+ gHitInfo.hitsect = pSprite->sectnum;
+ gHitInfo.hitx = pSprite->x;
+ gHitInfo.hity = pSprite->y;
+ gHitInfo.hitz = pSprite->z;
+ break;
+ }
+ if (pOwner)
+ pOwner->cstat = bakCstat;
+ return vdi;
+}
+
+void actExplodeSprite(spritetype *pSprite)
+{
+ int nXSprite = pSprite->extra;
+ if (nXSprite <= 0 || nXSprite >= kMaxXSprites)
+ return;
+ if (pSprite->statnum == 2)
+ return;
+ sfxKill3DSound(pSprite, -1, -1);
+ evKill(pSprite->index, 3);
+ int nType;
+ switch (pSprite->type)
+ {
+ case 312:
+ nType = 7;
+ seqSpawn(4, 3, nXSprite, -1);
+ if (Chance(0x8000))
+ pSprite->cstat |= 4;
+ sfxPlay3DSound(pSprite, 303, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ case 303:
+ nType = 3;
+ seqSpawn(9, 3, nXSprite, -1);
+ if (Chance(0x8000))
+ pSprite->cstat |= 4;
+ sfxPlay3DSound(pSprite, 306, 24+(pSprite->index&3), 1);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ case 313:
+ case 314:
+ nType = 3;
+ seqSpawn(5, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite, 304, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ case 418:
+ nType = 0;
+ if (gSpriteHit[nXSprite].florhit == 0)
+ seqSpawn(4,3,nXSprite,-1);
+ else
+ seqSpawn(3,3,nXSprite,-1);
+ sfxPlay3DSound(pSprite, 303, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ case 401:
+ case 402:
+ case 419:
+ case kGDXThingTNTProx:
+ nType = 1;
+ if (gSpriteHit[nXSprite].florhit == 0)
+ seqSpawn(4,3,nXSprite,-1);
+ else
+ seqSpawn(3,3,nXSprite,-1);
+ sfxPlay3DSound(pSprite, 304, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ case 420:
+ nType = 4;
+ seqSpawn(5, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite, 307, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ case 400:
+ {
+ spritetype *pSprite2 = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0, 1);
+ pSprite2->owner = pSprite->owner;
+ if (actCheckRespawn(pSprite))
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ pXSprite->state = 1;
+ pXSprite->health = thingInfo[0].at0<<4;
+ }
+ else
+ actPostSprite(pSprite->index, 1024);
+ nType = 2;
+ nXSprite = pSprite2->extra;
+ seqSpawn(4, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite2, 305, -1, 0);
+ GibSprite(pSprite2, GIBTYPE_14, NULL, NULL);
+ pSprite = pSprite2;
+ break;
+ }
+ case 459: // By NoOne: allow to customize hidden exploder thing
+ {
+ // Defaults for exploder
+ nType = 1; int nSnd = 304; int nSeq = 4;
+
+ // Temp variables for override via data fields
+ int tSnd = 0; int tSeq = 0;
+
+
+ XSPRITE *pXSPrite = &xsprite[nXSprite];
+ nType = pXSPrite->data1; // Explosion type
+ tSeq = pXSPrite->data2; // SEQ id
+ tSnd = pXSPrite->data3; // Sound Id
+
+ if (nType <= 1 || nType > kExplodeMax) { nType = 1; nSeq = 4; nSnd = 304; }
+ else if (nType == 2) { nSeq = 4; nSnd = 305; }
+ else if (nType == 3) { nSeq = 9; nSnd = 307; }
+ else if (nType == 4) { nSeq = 5; nSnd = 307; }
+ else if (nType <= 6) { nSeq = 4; nSnd = 303; }
+ else if (nType == 7) { nSeq = 4; nSnd = 303; }
+ else if (nType == 8) { nType = 0; nSeq = 3; nSnd = 303; }
+
+ // Override previous sound and seq assigns
+ if (tSeq > 0) nSeq = tSeq;
+ if (tSnd > 0) nSnd = tSnd;
+
+ //if (kExist(pXSPrite->data2, seq)) // GDX method to check if file exist in RFF
+ seqSpawn(nSeq, 3, nXSprite, -1);
+
+ sfxPlay3DSound(pSprite, nSnd, -1, 0);
+ }
+ break;
+ case 429:
+ nType = 3;
+ seqSpawn(9, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite, 307, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ sub_746D4(pSprite, 240);
+ break;
+ default:
+ nType = 1;
+ seqSpawn(4, 3, nXSprite, -1);
+ if (Chance(0x8000))
+ pSprite->cstat |= 4;
+ sfxPlay3DSound(pSprite, 303, -1, 0);
+ GibSprite(pSprite, GIBTYPE_5, NULL, NULL);
+ break;
+ }
+ int nSprite = pSprite->index;
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+ actPostSprite(nSprite, 2);
+ pSprite->xrepeat = pSprite->yrepeat = explodeInfo[nType].at0;
+ pSprite->hitag &= ~3;
+ pSprite->type = nType;
+ EXPLOSION *pExplodeInfo = &explodeInfo[nType];
+ xsprite[nXSprite].target = 0;
+ xsprite[nXSprite].data1 = pExplodeInfo->atf;
+ xsprite[nXSprite].data2 = pExplodeInfo->at13;
+ xsprite[nXSprite].data3 = pExplodeInfo->at17;
+}
+
+void actActivateGibObject(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int vdx = ClipRange(pXSprite->data1, 0, 31);
+ int vc = ClipRange(pXSprite->data2, 0, 31);
+ int v4 = ClipRange(pXSprite->data3, 0, 31);
+ int vbp = pXSprite->data4;
+ int v8 = pXSprite->dropMsg;
+ if (vdx > 0)
+ GibSprite(pSprite, (GIBTYPE)(vdx-1), NULL, NULL);
+ if (vc > 0)
+ GibSprite(pSprite, (GIBTYPE)(vc-1), NULL, NULL);
+ if (v4 > 0 && pXSprite->burnTime > 0)
+ GibSprite(pSprite, (GIBTYPE)(v4-1), NULL, NULL);
+ if (vbp > 0)
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, vbp, pSprite->sectnum);
+ if (v8 > 0)
+ actDropObject(pSprite, v8);
+ if (!(pSprite->cstat&32768) && !(pSprite->hitag&16))
+ actPostSprite(pSprite->index, 1024);
+}
+
+bool IsUnderWater(spritetype *pSprite)
+{
+ int nSector = pSprite->sectnum;
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0 && nXSector < kMaxXSectors)
+ if (xsector[nXSector].Underwater)
+ return 1;
+ return 0;
+}
+
+void MakeSplash(spritetype *pSprite, XSPRITE *pXSprite);
+
+void actProcessSprites(void)
+{
+ int nSprite;
+ int nNextSprite;
+ for (nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pSprite->type == 425 || pSprite->type == 426 || pSprite->type == 427)
+ if (pXSprite->locked && gFrameClock >= pXSprite->targetX)
+ pXSprite->locked = 0;
+ if (pXSprite->burnTime > 0)
+ {
+ pXSprite->burnTime = ClipLow(pXSprite->burnTime-4,0);
+ actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
+ }
+
+ // by NoOne: make Sight flag work and don't process sight flag for things which is locked or triggered
+ if (pXSprite->Sight && pXSprite->locked != 1 && pXSprite->isTriggered != true) {
+ for (int i = connecthead; i >= 0; i = connectpoint2[i]) {
+ PLAYER* pPlayer = &gPlayer[i]; int z = pPlayer->at6f - pPlayer->pSprite->z;
+ int hitCode = VectorScan(pPlayer->pSprite, 0, z, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 512000, 1);
+ if (hitCode != 3 || gHitInfo.hitsprite != pSprite->xvel) continue;
+ trTriggerSprite(nSprite, pXSprite, 34);
+ pXSprite->locked = 1; // lock it once triggered, so it can be unlocked again
+
+ break;
+ }
+ }
+ // by NoOne: don't process locked or 1-shot things for proximity
+ if (pXSprite->Proximity && pXSprite->locked != 1 && pXSprite->isTriggered != true) {
+ if (pSprite->type == 431) pXSprite->target = -1;
+ for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nNextSprite)
+ {
+
+ nNextSprite = nextspritestat[nSprite2];
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite2->hitag&32) continue;
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ if ((unsigned int)pXSprite2->health > 0)
+ {
+ int proxyDist = 96;
+ if (pSprite->type == kGDXThingCustomDudeLifeLeech) proxyDist = 512;
+ else if (pSprite->type == 431 && pXSprite->target == -1)
+ {
+ int nOwner = actOwnerIdToSpriteId(pSprite->owner);
+ spritetype *pOwner = &sprite[nOwner];
+ PLAYER *pPlayer = &gPlayer[pOwner->type-kDudePlayer1];
+ PLAYER *pPlayer2 = NULL;
+ if (IsPlayerSprite(pSprite2))
+ pPlayer2 = &gPlayer[pSprite2->type-kDudePlayer1];
+ if (nSprite2 == nOwner || pSprite2->type == 205 || pSprite2->type == 220 || pSprite2->type == 219)
+ continue;
+ if (gGameOptions.nGameType == 3 && pPlayer2 && pPlayer->at2ea == pPlayer2->at2ea)
+ continue;
+ if (gGameOptions.nGameType == 1 && pPlayer2)
+ continue;
+ proxyDist = 512;
+ }
+ if (CheckProximity(pSprite2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, proxyDist)) {
+
+ switch (pSprite->type) {
+ case kGDXThingTNTProx:
+ if (!IsPlayerSprite(pSprite2)) continue;
+ pSprite->pal = 0;
+ break;
+ case 431:
+ if (!Chance(0x4000) && nNextSprite >= 0) continue;
+ if (pSprite2->cstat & 0x10001) pXSprite->target = pSprite2->index;
+ else continue;
+ break;
+ case kGDXThingCustomDudeLifeLeech:
+ if (pXSprite->target != pSprite2->xvel) continue;
+ break;
+ }
+ if (pSprite->owner == -1) actPropagateSpriteOwner(pSprite, pSprite2);
+ trTriggerSprite(nSprite, pXSprite, 35);
+ }
+ }
+ }
+ }
+ }
+ }
+ for (nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ int nSector = pSprite->sectnum;
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ int nXSector = sector[nSector].extra;
+ XSECTOR *pXSector = NULL;
+ if (nXSector > 0)
+ {
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ dassert(xsector[nXSector].reference == nSector);
+ pXSector = &xsector[nXSector];
+ }
+ if (pXSector && pXSector->panVel && (pXSector->panAlways || pXSector->state || pXSector->busy))
+ {
+ int nType = pSprite->type - kThingBase;
+ THINGINFO *pThingInfo = &thingInfo[nType];
+ if (pThingInfo->at5 & 1)
+ pSprite->hitag |= 1;
+ if (pThingInfo->at5 & 2)
+ pSprite->hitag |= 4;
+ }
+ if (pSprite->hitag&3)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ if (pXSector && pXSector->panVel)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (getflorzofslope(nSector, pSprite->x, pSprite->y) <= bottom)
+ {
+ int angle = pXSector->panAngle;
+ int speed = 0;
+ if (pXSector->panAlways || pXSector->state || pXSector->busy)
+ {
+ speed = pXSector->panVel << 9;
+ if (!pXSector->panAlways && pXSector->busy)
+ speed = mulscale16(speed, pXSector->busy);
+ }
+ if (sector[nSector].floorstat&64)
+ angle = (angle+GetWallAngle(sector[nSector].wallptr)+512)&2047;
+ int dx = mulscale30(speed, Cos(angle));
+ int dy = mulscale30(speed, Sin(angle));
+ xvel[nSprite] += dx;
+ yvel[nSprite] += dy;
+ }
+ }
+ actAirDrag(pSprite, 128);
+ if ((pSprite->index>>8) == (gFrame&15) && (pSprite->hitag&2))
+ pSprite->hitag |= 4;
+ if ((pSprite->hitag&4) || xvel[nSprite] || yvel[nSprite] || zvel[nSprite] ||
+ velFloor[pSprite->sectnum] || velCeil[pSprite->sectnum])
+ {
+ int hit = MoveThing(pSprite);
+ if (hit)
+ {
+ int nXSprite = pSprite->extra;
+ if (nXSprite)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->Impact)
+ trTriggerSprite(nSprite, pXSprite, 0);
+ switch (pSprite->type)
+ {
+ case 423:
+ case 424:
+ MakeSplash(pSprite, pXSprite);
+ break;
+ case kGDXThingThrowableRock:
+ seqSpawn(24, 3, nXSprite, -1);
+ if ((hit & 0xe000) == 0xc000)
+ {
+ pSprite->xrepeat = 32;
+ pSprite->yrepeat = 32;
+ int nObject = hit & 0x1fff;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype * pObject = &sprite[nObject];
+ actDamageSprite(actSpriteOwnerToSpriteId(pSprite), pObject, DAMAGE_TYPE_0, pXSprite->data1);
+ }
+ break;
+ case 421:
+ seqSpawn(24, 3, nXSprite, -1);
+ if ((hit&0xe000) == 0xc000)
+ {
+ int nObject = hit & 0x1fff;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ actDamageSprite(actSpriteOwnerToSpriteId(pSprite), pObject, DAMAGE_TYPE_0, 12);
+ }
+ break;
+ case 430:
+ if ((hit&0xe000) == 0x4000)
+ {
+ sub_2A620(actSpriteOwnerToSpriteId(pSprite), pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 200, 1, 20, DAMAGE_TYPE_3, 6, 0, 0, 0);
+ evPost(pSprite->index, 3, 0, CALLBACK_ID_19);
+ }
+ else
+ {
+ int nObject = hit & 0x1fff;
+ if ((hit&0xe000) != 0xc000 && (nObject < 0 || nObject >= 4096))
+ break;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ spritetype *pObject = &sprite[nObject];
+ actDamageSprite(actSpriteOwnerToSpriteId(pSprite), pObject, DAMAGE_TYPE_0, 12);
+ evPost(pSprite->index, 3, 0, CALLBACK_ID_19);
+ }
+ break;
+ case 429:
+ {
+ int nObject = hit & 0x1fff;
+ if ((hit&0xe000) != 0xc000 && (nObject < 0 || nObject >= 4096))
+ break;
+ dassert(nObject >= 0 && nObject < kMaxSprites);
+ int UNUSED(nOwner) = actSpriteOwnerToSpriteId(pSprite);
+ actExplodeSprite(pSprite);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ for (nSprite = headspritestat[5]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ viewBackupSpriteLoc(nSprite, pSprite);
+ int hit = MoveMissile(pSprite);
+ if (hit >= 0)
+ actImpactMissile(pSprite, hit);
+ }
+ for (nSprite = headspritestat[2]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ char v24c[(kMaxSectors+7)>>3];
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ int nOwner = actSpriteOwnerToSpriteId(pSprite);
+ int nType = pSprite->type;
+ dassert(nType >= 0 && nType < kExplodeMax);
+ EXPLOSION *pExplodeInfo = &explodeInfo[nType];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int nSector = pSprite->sectnum;
+ gAffectedSectors[0] = -1;
+ gAffectedXWalls[0] = -1;
+
+ // By NoOne: Allow to override explosion radius by data4 field of any sprite which have statnum 2 set in editor
+ // or of Hidden Exploder.
+ int radius = pXSprite->data4;
+ if (pXSprite->data4 <= 0)
+ radius = pExplodeInfo->at3;
+
+ GetClosestSpriteSectors(nSector, x, y, radius, gAffectedSectors, v24c, gAffectedXWalls);
+ for (int i = 0; i < kMaxXWalls; i++)
+ {
+ int nWall = gAffectedXWalls[i];
+ if (nWall == -1)
+ break;
+ XWALL *pXWall = &xwall[wall[nWall].extra];
+ trTriggerWall(nWall, pXWall, 51);
+ }
+ for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pDude = &sprite[nSprite2];
+ if (pDude->hitag & 32)
+ continue;
+ if (TestBitString(v24c, pDude->sectnum))
+ {
+ if (pXSprite->data1 && CheckProximity(pDude, x, y, z, nSector, radius))
+ {
+ if (pExplodeInfo->at1 && pXSprite->target == 0)
+ {
+ pXSprite->target = 1;
+ actDamageSprite(nOwner, pDude, DAMAGE_TYPE_0, (pExplodeInfo->at1+Random(pExplodeInfo->at2))<<4);
+ }
+ if (pExplodeInfo->at7)
+ ConcussSprite(nOwner, pDude, x, y, z, pExplodeInfo->at7);
+ if (pExplodeInfo->atb)
+ {
+ dassert(pDude->extra > 0 && pDude->extra < kMaxXSprites);
+ XSPRITE *pXDude = &xsprite[pDude->extra];
+ if (!pXDude->burnTime)
+ evPost(nSprite2, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(pSprite->owner, pXDude, pExplodeInfo->atb<<2);
+ }
+ }
+ }
+ }
+ for (int nSprite2 = headspritestat[4]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pThing = &sprite[nSprite2];
+ if (pThing->hitag & 32)
+ continue;
+ if (TestBitString(v24c, pThing->sectnum))
+ {
+ if (pXSprite->data1 && CheckProximity(pThing, x, y, z, nSector, radius))
+ {
+ XSPRITE *pXSprite2 = &xsprite[pThing->extra];
+ if (!pXSprite2->locked)
+ {
+ if (pExplodeInfo->at7)
+ ConcussSprite(nOwner, pThing, x, y, z, pExplodeInfo->at7);
+ if (pExplodeInfo->atb)
+ {
+ dassert(pThing->extra > 0 && pThing->extra < kMaxXSprites);
+ XSPRITE *pXThing = &xsprite[pThing->extra];
+ if (pThing->type == 400 && !pXThing->burnTime)
+ evPost(nSprite2, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(pSprite->owner, pXThing, pExplodeInfo->atb<<2);
+ }
+ }
+ }
+ }
+ }
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ spritetype *pSprite2 = gPlayer[p].pSprite;
+ int dx = (x - pSprite2->x)>>4;
+ int dy = (y - pSprite2->y)>>4;
+ int dz = (z - pSprite2->z)>>8;
+ int nDist = dx*dx+dy*dy+dz*dz+0x40000;
+ int t = divscale16(pXSprite->data2, nDist);
+ gPlayer[p].at35a += t;
+ }
+
+ // By NoOne: if data4 > 0, do not remove explosion. This can be useful when designer wants put explosion generator in map manually
+ // via sprite statnum 2.
+ if (!(pSprite->hitag & kHitagExtBit)) {
+ pXSprite->data1 = ClipLow(pXSprite->data1 - 4, 0);
+ pXSprite->data2 = ClipLow(pXSprite->data2 - 4, 0);
+ pXSprite->data3 = ClipLow(pXSprite->data3 - 4, 0);
+ }
+
+ if (pXSprite->data1 == 0 && pXSprite->data2 == 0 && pXSprite->data3 == 0 && seqGetStatus(3, nXSprite) < 0)
+ actPostSprite(nSprite, 1024);
+ }
+ for (nSprite = headspritestat[11]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ switch (pSprite->type)
+ {
+ case 454:
+ pXSprite->data2 = ClipLow(pXSprite->data2-4, 0);
+ break;
+ case 452:
+ if (pXSprite->state && seqGetStatus(3, nXSprite) < 0)
+ {
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int t = (pXSprite->data1<<23)/120;
+ int dx = mulscale30(t, Cos(pSprite->ang));
+ int dy = mulscale30(t, Sin(pSprite->ang));
+ for (int i = 0; i < 2; i++)
+ {
+ spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = dx + Random2(0x8888);
+ yvel[pFX->index] = dy + Random2(0x8888);
+ zvel[pFX->index] = Random2(0x8888);
+ }
+ x += (dx/2)>>12;
+ y += (dy/2)>>12;
+ }
+ dy = Sin(pSprite->ang)>>16;
+ dx = Cos(pSprite->ang)>>16;
+ gVectorData[VECTOR_TYPE_20].maxDist = pXSprite->data1<<9;
+ actFireVector(pSprite, 0, 0, dx, dy, Random2(0x8888), VECTOR_TYPE_20);
+ }
+ break;
+ }
+ }
+ for (nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->burnTime > 0)
+ {
+ switch (pSprite->type)
+ {
+ case 239:
+ case 240:
+ case 241:
+ case 242:
+ actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
+ break;
+ default:
+ pXSprite->burnTime = ClipLow(pXSprite->burnTime-4, 0);
+ actDamageSprite(actOwnerIdToSpriteId(pXSprite->burnSource), pSprite, DAMAGE_TYPE_1, 8);
+ break;
+ }
+ }
+
+ // By NoOne: handle incarnations of custom dude
+ if (pSprite->type == kGDXDudeUniversalCultist && pXSprite->health <= 0 && seqGetStatus(3, nXSprite) < 0) {
+ XSPRITE* pXIncarnation = getNextIncarnation(pXSprite);
+ if (pXIncarnation != NULL) {
+ spritetype* pIncarnation = &sprite[pXIncarnation->reference];
+
+ pSprite->type = pIncarnation->type;
+ pSprite->pal = pIncarnation->pal;
+ pSprite->shade = pIncarnation->shade;
+
+ pXSprite->txID = pXIncarnation->txID;
+ pXSprite->command = pXIncarnation->command;
+ pXSprite->triggerOn = pXIncarnation->triggerOn;
+ pXSprite->triggerOff = pXIncarnation->triggerOff;
+
+ pXSprite->burnTime = 0;
+ pXSprite->burnSource = -1;
+
+ pXSprite->data1 = pXIncarnation->data1;
+ pXSprite->data2 = pXIncarnation->data2;
+ pXSprite->data3 = pXIncarnation->data3;
+ pXSprite->data4 = pXIncarnation->data4;
+
+ pXSprite->dudeGuard = pXIncarnation->dudeGuard;
+ pXSprite->dudeDeaf = pXIncarnation->dudeDeaf;
+ pXSprite->dudeAmbush = pXIncarnation->dudeAmbush;
+ pXSprite->dudeFlag4 = pXIncarnation->dudeFlag4;
+
+ pXSprite->busyTime = pXIncarnation->busyTime;
+ aiInitSprite(pSprite);
+ switch (pSprite->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ if (pXSprite->data2 > 0) seqSpawn(pXSprite->data2, 3, nXSprite, -1);
+ else seqSpawn(dudeInfo[pSprite->type - kDudeBase].seqStartID, 3, nXSprite, -1);
+ break;
+ default:
+ seqSpawn(dudeInfo[pSprite->type - kDudeBase].seqStartID, 3, nXSprite, -1);
+ break;
+ }
+
+ if (pXSprite->data4 > 0) pXSprite->health = pXSprite->data4;
+ else pXSprite->health = dudeInfo[pSprite->type - kDudeBase].startHealth << 4;
+ aiActivateDude(pSprite, pXSprite);
+ }
+ }
+
+ if (pSprite->type == 227)
+ {
+ if (pXSprite->health <= 0 && seqGetStatus(3, nXSprite) < 0)
+ {
+ pXSprite->health = dudeInfo[28].startHealth<<4;
+ pSprite->type = 228;
+ if (pXSprite->target != -1)
+ aiSetTarget(pXSprite, pXSprite->target);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ }
+ if (pXSprite->Proximity && !pXSprite->isTriggered)
+ {
+ for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nNextSprite)
+ {
+ nNextSprite = nextspritestat[nSprite2];
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite2->hitag&32)
+ continue;
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ if ((unsigned int)pXSprite2->health > 0 && pSprite2->type >= kDudePlayer1 && pSprite2->type <= kDudePlayer8)
+ {
+ if (CheckProximity(pSprite2, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, 128))
+ {
+ trTriggerSprite(nSprite, pXSprite, 35);
+ }
+ }
+ }
+ }
+ if (IsPlayerSprite(pSprite))
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ if (pPlayer->at34e)
+ sub_41250(pPlayer);
+ if (pPlayer->at376 && Chance(0x8000))
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_4, 12);
+ if (pPlayer->at87)
+ {
+ char bActive = packItemActive(pPlayer, 1);
+ if (bActive || pPlayer->at31a)
+ pPlayer->at2f2 = 1200;
+ else
+ pPlayer->at2f2 = ClipLow(pPlayer->at2f2-4, 0);
+ if (pPlayer->at2f2 < 1080 && packCheckItem(pPlayer, 1) && !bActive)
+ packUseItem(pPlayer, 1);
+ if (!pPlayer->at2f2)
+ {
+ pPlayer->at36e += 4;
+ if (Chance(pPlayer->at36e))
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_4, 3<<4);
+ }
+ else
+ pPlayer->at36e = 0;
+ if (xvel[nSprite] || yvel[nSprite])
+ sfxPlay3DSound(pSprite, 709, 100, 2);
+ pPlayer->at302 = ClipLow(pPlayer->at302-4, 0);
+ }
+ else if (gGameOptions.nGameType == 0)
+ {
+ if (pPlayer->pXSprite->health > 0 && pPlayer->at30a >= 1200 && Chance(0x200))
+ {
+ pPlayer->at30a = -1;
+ sfxPlay3DSound(pSprite, 3100+Random(11), 0, 2);
+ }
+ }
+ }
+ ProcessTouchObjects(pSprite, nXSprite);
+ }
+ }
+ for (nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ int nSector = pSprite->sectnum;
+ viewBackupSpriteLoc(nSprite, pSprite);
+ int nXSector = sector[nSector].extra;
+ XSECTOR *pXSector = NULL;
+ if (nXSector > 0)
+ {
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ dassert(xsector[nXSector].reference == nSector);
+ pXSector = &xsector[nXSector];
+ }
+ if (pXSector)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (getflorzofslope(nSector, pSprite->x, pSprite->y) <= bottom)
+ {
+ int angle = pXSector->panAngle;
+ int speed = 0;
+ if (pXSector->panAlways || pXSector->state || pXSector->busy)
+ {
+ speed = pXSector->panVel << 9;
+ if (!pXSector->panAlways && pXSector->busy)
+ speed = mulscale16(speed, pXSector->busy);
+ }
+ if (sector[nSector].floorstat&64)
+ angle = (angle+GetWallAngle(sector[nSector].wallptr)+512)&2047;
+ int dx = mulscale30(speed, Cos(angle));
+ int dy = mulscale30(speed, Sin(angle));
+ xvel[nSprite] += dx;
+ yvel[nSprite] += dy;
+ }
+ }
+ if (pXSector && pXSector->Underwater)
+ actAirDrag(pSprite, 5376);
+ else
+ actAirDrag(pSprite, 128);
+ if ((pSprite->hitag&4) || xvel[nSprite] || yvel[nSprite] || zvel[nSprite] ||
+ velFloor[pSprite->sectnum] || velCeil[pSprite->sectnum])
+ MoveDude(pSprite);
+ }
+ for (nSprite = headspritestat[14]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag & 32)
+ continue;
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nTarget = pXSprite->target;
+ dassert(nTarget >= 0);
+ viewBackupSpriteLoc(nSprite, pSprite);
+ dassert(nTarget < kMaxSprites);
+ spritetype *pTarget = &sprite[nTarget];
+ if (pTarget->statnum == kMaxStatus)
+ {
+ GibSprite(pSprite, GIBTYPE_17, NULL, NULL);
+ actPostSprite(pSprite->index, 1024);
+ }
+ if (pTarget->extra && xsprite[pTarget->extra].health > 0)
+ {
+ int x = pTarget->x+mulscale30r(Cos(pXSprite->goalAng+pTarget->ang), pTarget->clipdist*2);
+ int y = pTarget->y+mulscale30r(Sin(pXSprite->goalAng+pTarget->ang), pTarget->clipdist*2);
+ int z = pTarget->z+pXSprite->targetZ;
+ vec3_t pos = { x, y, z };
+ setsprite(nSprite,&pos);
+ xvel[nSprite] = xvel[nTarget];
+ yvel[nSprite] = yvel[nTarget];
+ zvel[nSprite] = zvel[nTarget];
+ }
+ else
+ {
+ GibSprite(pSprite, GIBTYPE_17, NULL, NULL);
+ actPostSprite(pSprite->index, 1024);
+ }
+ }
+ aiProcessDudes();
+ gFX.fxProcess();
+}
+
+spritetype * actSpawnSprite(int nSector, int x, int y, int z, int nStat, char a6)
+{
+ int nSprite = InsertSprite(nSector, nStat);
+ if (nSprite >= 0)
+ sprite[nSprite].extra = -1;
+ else
+ {
+ nSprite = headspritestat[9];
+ dassert(nSprite >= 0);
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ ChangeSpriteSect(nSprite, nSector);
+ actPostSprite(nSprite, nStat);
+ }
+ vec3_t pos = { x, y, z };
+ setsprite(nSprite, &pos);
+ spritetype *pSprite = &sprite[nSprite];
+ pSprite->type = 0;
+ if (a6 && pSprite->extra == -1)
+ {
+ int nXSprite = dbInsertXSprite(nSprite);
+ gSpriteHit[nXSprite].florhit = 0;
+ gSpriteHit[nXSprite].ceilhit = 0;
+ }
+ return pSprite;
+}
+
+spritetype * actSpawnSprite(spritetype *pSource, int nStat);
+
+spritetype *actSpawnDude(spritetype *pSource, short nType, int a3, int a4)
+{
+ XSPRITE* pXSource = &xsprite[pSource->extra];
+ spritetype *pSprite2 = actSpawnSprite(pSource, 6);
+ if (!pSprite2) return NULL;
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ int angle = pSource->ang;
+ int nDude = nType-kDudeBase;
+ int x, y, z;
+ z = a4 + pSource->z;
+ if (a3 < 0)
+ {
+ x = pSource->x;
+ y = pSource->y;
+ }
+ else
+ {
+ x = pSource->x+mulscale30r(Cos(angle), a3);
+ y = pSource->y+mulscale30r(Sin(angle), a3);
+ }
+ pSprite2->type = nType;
+ pSprite2->ang = angle;
+ vec3_t pos = { x, y, z };
+ setsprite(pSprite2->index, &pos);
+ pSprite2->cstat |= 0x1101;
+ pSprite2->clipdist = dudeInfo[nDude].clipdist;
+ pXSprite2->health = dudeInfo[nDude].startHealth<<4;
+ if (gSysRes.Lookup(dudeInfo[nDude].seqStartID, "SEQ"))
+ seqSpawn(dudeInfo[nDude].seqStartID, 3, pSprite2->extra, -1);
+
+ // By NoOne: add a way to inherit some values of spawner type 18 by dude.
+ // This way designer can count enemies via switches and do many other interesting things.
+
+ // oops, forget to check for source type previously
+ if ((pSource->hitag & kHitagExtBit) != 0 && pSource->type == 18) {
+
+ //inherit pal?
+ if (pSprite2->pal <= 0) pSprite2->pal = pSource->pal;
+
+ // inherit spawn sprite trigger settings, so designer can count monsters.
+ pXSprite2->txID = pXSource->txID;
+ pXSprite2->command = pXSource->command;
+ pXSprite2->triggerOn = pXSource->triggerOn;
+ pXSprite2->triggerOff = pXSource->triggerOff;
+
+ // inherit drop items
+ pXSprite2->dropMsg = pXSource->dropMsg;
+
+ // inherit dude flags
+ pXSprite2->dudeDeaf = pXSource->dudeDeaf;
+ pXSprite2->dudeGuard = pXSource->dudeGuard;
+ pXSprite2->dudeAmbush = pXSource->dudeAmbush;
+ pXSprite2->dudeFlag4 = pXSource->dudeFlag4;
+ }
+
+ aiInitSprite(pSprite2);
+ return pSprite2;
+}
+
+spritetype * actSpawnSprite(spritetype *pSource, int nStat)
+{
+ int nSprite = InsertSprite(pSource->sectnum, nStat);
+ if (nSprite < 0)
+ {
+ nSprite = headspritestat[9];
+ dassert(nSprite >= 0);
+ dassert(pSource->sectnum >= 0 && pSource->sectnum < kMaxSectors);
+ ChangeSpriteSect(nSprite, pSource->sectnum);
+ actPostSprite(nSprite, nStat);
+ }
+ spritetype *pSprite = &sprite[nSprite];
+ pSprite->x = pSource->x;
+ pSprite->y = pSource->y;
+ pSprite->z = pSource->z;
+ xvel[nSprite] = xvel[pSource->index];
+ yvel[nSprite] = yvel[pSource->index];
+ zvel[nSprite] = zvel[pSource->index];
+ pSprite->hitag = 0;
+ int nXSprite = dbInsertXSprite(nSprite);
+ gSpriteHit[nXSprite].florhit = 0;
+ gSpriteHit[nXSprite].ceilhit = 0;
+ return pSprite;
+}
+
+spritetype * actSpawnThing(int nSector, int x, int y, int z, int nThingType)
+{
+ dassert(nThingType >= kThingBase && nThingType < kThingMax);
+ spritetype *pSprite = actSpawnSprite(nSector, x, y, z, 4, 1);
+ int nType = nThingType-kThingBase;
+ int nThing = pSprite->index;
+ int nXThing = pSprite->extra;
+ pSprite->type = nThingType;
+ dassert(nXThing > 0 && nXThing < kMaxXSprites);
+ XSPRITE *pXThing = &xsprite[nXThing];
+ THINGINFO *pThingInfo = &thingInfo[nType];
+ pXThing->health = pThingInfo->at0<<4;
+ pSprite->clipdist = pThingInfo->at4;
+ pSprite->hitag = pThingInfo->at5;
+ if (pSprite->hitag & 2)
+ pSprite->hitag |= 4;
+ pSprite->cstat |= pThingInfo->atf;
+ pSprite->picnum = pThingInfo->at11;
+ pSprite->shade = pThingInfo->at13;
+ pSprite->pal = pThingInfo->at14;
+ if (pThingInfo->at15)
+ pSprite->xrepeat = pThingInfo->at15;
+ if (pThingInfo->at16)
+ pSprite->yrepeat = pThingInfo->at16;
+ SetBitString(show2dsprite, pSprite->index);
+ switch (nThingType)
+ {
+ case 432:
+ pXThing->data1 = 0;
+ pXThing->data2 = 0;
+ pXThing->data3 = 0;
+ pXThing->data4 = 0;
+ pXThing->state = 1;
+ pXThing->triggerOnce = 1;
+ pXThing->isTriggered = 0;
+ break;
+ case 431:
+ case kGDXThingCustomDudeLifeLeech:
+ pXThing->data1 = 0;
+ pXThing->data2 = 0;
+ pXThing->data3 = 0;
+ pXThing->data4 = 0;
+ pXThing->state = 1;
+ pXThing->triggerOnce = 0;
+ pXThing->isTriggered = 0;
+ break;
+ case 427:
+ pXThing->data1 = 8;
+ pXThing->data2 = 0;
+ pXThing->data3 = 0;
+ pXThing->data4 = 318;
+ pXThing->targetX = gFrameClock+180.0;
+ pXThing->locked = 1;
+ pXThing->state = 1;
+ pXThing->triggerOnce = 0;
+ pXThing->isTriggered = 0;
+ break;
+ case 425:
+ case 426:
+ if (nThingType == 425)
+ pXThing->data1 = 19;
+ else if (nThingType == 426)
+ pXThing->data1 = 8;
+ pXThing->data2 = 0;
+ pXThing->data3 = 0;
+ pXThing->data4 = 318;
+ pXThing->targetX = gFrameClock+180.0;
+ pXThing->locked = 1;
+ pXThing->state = 1;
+ pXThing->triggerOnce = 0;
+ pXThing->isTriggered = 0;
+ break;
+ case 418:
+ evPost(nThing, 3, 0, CALLBACK_ID_8);
+ sfxPlay3DSound(pSprite, 450, 0, 0);
+ break;
+ case 419:
+ sfxPlay3DSound(pSprite, 450, 0, 0);
+ evPost(nThing, 3, 0, CALLBACK_ID_8);
+ break;
+ case 420:
+ evPost(nThing, 3, 0, CALLBACK_ID_8);
+ break;
+ }
+ return pSprite;
+}
+
+spritetype * actFireThing(spritetype *pSprite, int a2, int a3, int a4, int thingType, int a6)
+{
+ dassert(thingType >= kThingBase && thingType < kThingMax);
+ int x = pSprite->x+mulscale30(a2, Cos(pSprite->ang+512));
+ int y = pSprite->y+mulscale30(a2, Sin(pSprite->ang+512));
+ int z = pSprite->z+a3;
+ x += mulscale28(pSprite->clipdist, Cos(pSprite->ang));
+ y += mulscale28(pSprite->clipdist, Sin(pSprite->ang));
+ if (HitScan(pSprite, z, x-pSprite->x, y-pSprite->y, 0, CLIPMASK0, pSprite->clipdist) != -1)
+ {
+ x = gHitInfo.hitx-mulscale28(pSprite->clipdist<<1, Cos(pSprite->ang));
+ y = gHitInfo.hity-mulscale28(pSprite->clipdist<<1, Sin(pSprite->ang));
+ }
+ spritetype *pThing = actSpawnThing(pSprite->sectnum, x, y, z, thingType);
+ actPropagateSpriteOwner(pThing, pSprite);
+ pThing->ang = pSprite->ang;
+ xvel[pThing->index] = mulscale30(a6, Cos(pThing->ang));
+ yvel[pThing->index] = mulscale30(a6, Sin(pThing->ang));
+ zvel[pThing->index] = mulscale(a6, a4, 14);
+ xvel[pThing->index] += xvel[pSprite->index]/2;
+ yvel[pThing->index] += yvel[pSprite->index]/2;
+ zvel[pThing->index] += zvel[pSprite->index]/2;
+ return pThing;
+}
+
+spritetype* actFireMissile(spritetype *pSprite, int a2, int a3, int a4, int a5, int a6, int nType)
+{
+ dassert(nType >= kMissileBase && nType < kMissileMax);
+ char v4 = 0;
+ int nSprite = pSprite->index;
+ MissileType *pMissileInfo = &missileInfo[nType-kMissileBase];
+ int x = pSprite->x+mulscale30(a2, Cos(pSprite->ang+512));
+ int y = pSprite->y+mulscale30(a2, Sin(pSprite->ang+512));
+ int z = pSprite->z+a3;
+ int clipdist = pMissileInfo->atd+pSprite->clipdist;
+ x += mulscale28(clipdist, Cos(pSprite->ang));
+ y += mulscale28(clipdist, Sin(pSprite->ang));
+ int hit = HitScan(pSprite, z, x-pSprite->x, y-pSprite->y, 0, CLIPMASK0, clipdist);
+ if (hit != -1)
+ {
+ if (hit == 3 || hit == 0)
+ {
+ v4 = 1;
+ x = gHitInfo.hitx-mulscale30(Cos(pSprite->ang), 16);
+ y = gHitInfo.hity-mulscale30(Sin(pSprite->ang), 16);
+ }
+ else
+ {
+ x = gHitInfo.hitx-mulscale28(pMissileInfo->atd<<1, Cos(pSprite->ang));
+ y = gHitInfo.hity-mulscale28(pMissileInfo->atd<<1, Sin(pSprite->ang));
+ }
+ }
+ spritetype *pMissile = actSpawnSprite(pSprite->sectnum, x, y, z, 5, 1);
+ int nMissile = pMissile->index;
+ SetBitString(show2dsprite, nMissile);
+ pMissile->type = nType;
+ pMissile->shade = pMissileInfo->atc;
+ pMissile->pal = 0;
+ pMissile->clipdist = pMissileInfo->atd;
+ pMissile->hitag = 1;
+ pMissile->xrepeat = pMissileInfo->ata;
+ pMissile->yrepeat = pMissileInfo->atb;
+ pMissile->picnum = pMissileInfo->picnum;
+ pMissile->ang = (pSprite->ang+pMissileInfo->at6)&2047;
+ xvel[nMissile] = mulscale(pMissileInfo->at2, a4, 14);
+ yvel[nMissile] = mulscale(pMissileInfo->at2, a5, 14);
+ zvel[nMissile] = mulscale(pMissileInfo->at2, a6, 14);
+ actPropagateSpriteOwner(pMissile, pSprite);
+ pMissile->cstat |= 1;
+ int nXSprite = pMissile->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ xsprite[nXSprite].target = -1;
+ evPost(nMissile, 3, 600, CALLBACK_ID_1);
+ switch (nType)
+ {
+ case 315:
+ evPost(nMissile, 3, 0, CALLBACK_ID_0);
+ break;
+ case 302:
+ evPost(nMissile, 3, 0, CALLBACK_ID_15);
+ break;
+ case 309:
+ seqSpawn(29, 3, nXSprite, -1);
+ break;
+ case 300:
+ pMissile->cstat |= 16;
+ break;
+ case 306:
+ sfxPlay3DSound(pMissile, 251, 0, 0);
+ break;
+ case 307:
+ seqSpawn(2, 3, nXSprite, -1);
+ sfxPlay3DSound(pMissile, 493, 0, 0);
+ break;
+ case 312:
+ seqSpawn(61, 3, nXSprite, nNapalmClient);
+ sfxPlay3DSound(pMissile, 441, 0, 0);
+ break;
+ case 305:
+ seqSpawn(22, 3, nXSprite, nFireballClient);
+ sfxPlay3DSound(pMissile, 441, 0, 0);
+ break;
+ case 308:
+ seqSpawn(27, 3, nXSprite, -1);
+ xvel[nMissile] += xvel[nSprite]/2+Random2(0x11111);
+ yvel[nMissile] += yvel[nSprite]/2+Random2(0x11111);
+ zvel[nMissile] += zvel[nSprite]/2+Random2(0x11111);
+ break;
+ case 313:
+ seqSpawn(61, 3, nXSprite, dword_2192E0);
+ sfxPlay3DSound(pMissile, 441, 0, 0);
+ break;
+ case 314:
+ seqSpawn(23, 3, nXSprite, dword_2192D8);
+ xvel[nMissile] += xvel[nSprite]/2+Random2(0x11111);
+ yvel[nMissile] += yvel[nSprite]/2+Random2(0x11111);
+ zvel[nMissile] += zvel[nSprite]/2+Random2(0x11111);
+ break;
+ case 304:
+ if (Chance(0x8000))
+ seqSpawn(0, 3, nXSprite, -1);
+ else
+ seqSpawn(1, 3, nXSprite, -1);
+ xvel[nMissile] += xvel[nSprite]+Random2(0x11111);
+ yvel[nMissile] += yvel[nSprite]+Random2(0x11111);
+ zvel[nMissile] += zvel[nSprite]+Random2(0x11111);
+ break;
+ case 303:
+ evPost(nMissile, 3, 30, CALLBACK_ID_2);
+ evPost(nMissile, 3, 0, CALLBACK_ID_3);
+ sfxPlay3DSound(pMissile, 422, 0, 0);
+ break;
+ case 301:
+ evPost(nMissile, 3, 0, CALLBACK_ID_3);
+ sfxPlay3DSound(pMissile, 422, 0, 0);
+ break;
+ case 317:
+ evPost(nMissile, 3, 0, CALLBACK_ID_7);
+ break;
+ case 311:
+ sfxPlay3DSound(pMissile, 252, 0, 0);
+ break;
+ }
+ if (v4)
+ {
+ actImpactMissile(pMissile, hit);
+ pMissile = NULL;
+ }
+ return pMissile;
+}
+
+int actGetRespawnTime(spritetype *pSprite)
+{
+ int nXSprite = pSprite->extra;
+ if (nXSprite <= 0)
+ return -1;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (IsDudeSprite(pSprite) && !IsPlayerSprite(pSprite))
+ {
+ if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nMonsterSettings == 2))
+ return gGameOptions.nMonsterRespawnTime;
+ return -1;
+ }
+ if (IsWeaponSprite(pSprite))
+ {
+ if (pXSprite->respawn == 3 || gGameOptions.nWeaponSettings == 1)
+ return 0;
+ if (pXSprite->respawn != 1 && gGameOptions.nWeaponSettings != 0)
+ return gGameOptions.nWeaponRespawnTime;
+ return -1;
+ }
+ if (IsAmmoSprite(pSprite))
+ {
+ if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nWeaponSettings != 0))
+ return gGameOptions.nWeaponRespawnTime;
+ return -1;
+ }
+ if (IsItemSprite(pSprite))
+ {
+ if (pXSprite->respawn == 3 && gGameOptions.nGameType == 1)
+ return 0;
+ if (pXSprite->respawn == 2 || (pXSprite->respawn != 1 && gGameOptions.nItemSettings != 0))
+ {
+ switch (pSprite->type)
+ {
+ case 113:
+ case 117:
+ case 124:
+ return gGameOptions.nSpecialRespawnTime;
+ case 114:
+ return gGameOptions.nSpecialRespawnTime<<1;
+ default:
+ return gGameOptions.nItemRespawnTime;
+ }
+ }
+ return -1;
+ }
+ return -1;
+}
+
+bool actCheckRespawn(spritetype *pSprite)
+{
+ int nSprite = pSprite->index;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nRespawnTime = actGetRespawnTime(pSprite);
+ if (nRespawnTime < 0)
+ return 0;
+ pXSprite->respawnPending = 1;
+ if (pSprite->type >= kThingBase && pSprite->type < kThingMax)
+ {
+ pXSprite->respawnPending = 3;
+ if (pSprite->type == 400)
+ pSprite->cstat |= 32768;
+ }
+ if (nRespawnTime > 0)
+ {
+ if (pXSprite->respawnPending == 1)
+ nRespawnTime = mulscale16(nRespawnTime, 0xa000);
+ pSprite->owner = pSprite->statnum;
+ actPostSprite(pSprite->index, 8);
+ pSprite->hitag |= 16;
+ if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ {
+ pSprite->cstat &= ~257;
+ pSprite->x = baseSprite[nSprite].x;
+ pSprite->y = baseSprite[nSprite].y;
+ pSprite->z = baseSprite[nSprite].z;
+ }
+ evPost(nSprite, 3, nRespawnTime, CALLBACK_ID_9);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+bool actCanSplatWall(int nWall)
+{
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ walltype *pWall = &wall[nWall];
+ if (pWall->cstat & 16384)
+ return 0;
+ if (pWall->cstat & 32768)
+ return 0;
+ if (pWall->lotag >= 500 && pWall->lotag < 512)
+ return 0;
+ if (pWall->nextsector != -1)
+ {
+ sectortype *pSector = §or[pWall->nextsector];
+ if (pSector->lotag >= 600 && pSector->lotag < 620)
+ return 0;
+ }
+ return 1;
+}
+
+void actFireVector(spritetype *pShooter, int a2, int a3, int a4, int a5, int a6, VECTOR_TYPE vectorType)
+{
+ int nShooter = pShooter->index;
+ dassert(vectorType >= 0 && vectorType < kVectorMax);
+ VECTORDATA *pVectorData = &gVectorData[vectorType];
+ int nRange = pVectorData->maxDist;
+ int hit = VectorScan(pShooter, a2, a3, a4, a5, a6, nRange, 1);
+ if (hit == 3)
+ {
+ int nSprite = gHitInfo.hitsprite;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ if (IsPlayerSprite(pSprite))
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ if (powerupCheck(pPlayer, 24))
+ {
+ gHitInfo.hitsprite = nShooter;
+ gHitInfo.hitx = pShooter->x;
+ gHitInfo.hity = pShooter->y;
+ gHitInfo.hitz = pShooter->z;
+ }
+ }
+ }
+ int x = gHitInfo.hitx-mulscale(a4, 16, 14);
+ int y = gHitInfo.hity-mulscale(a5, 16, 14);
+ int z = gHitInfo.hitz-mulscale(a6, 256, 14);
+ short nSector = gHitInfo.hitsect;
+ char nSurf = 0;
+ if (nRange == 0 || approxDist(gHitInfo.hitx-pShooter->x, gHitInfo.hity-pShooter->y) < nRange)
+ {
+ switch (hit)
+ {
+ case 1:
+ {
+ int nSector = gHitInfo.hitsect;
+ if (sector[nSector].ceilingstat&1)
+ nSurf = 0;
+ else
+ nSurf = surfType[sector[nSector].ceilingpicnum];
+ break;
+ }
+ case 2:
+ {
+ int nSector = gHitInfo.hitsect;
+ if (sector[nSector].floorstat&1)
+ nSurf = 0;
+ else
+ nSurf = surfType[sector[nSector].floorpicnum];
+ break;
+ }
+ case 0:
+ {
+ int nWall = gHitInfo.hitwall;
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ nSurf = surfType[wall[nWall].picnum];
+ if (actCanSplatWall(nWall))
+ {
+ int x = gHitInfo.hitx-mulscale(a4, 16, 14);
+ int y = gHitInfo.hity-mulscale(a5, 16, 14);
+ int z = gHitInfo.hitz-mulscale(a6, 256, 14);
+ int nSurf = surfType[wall[nWall].picnum];
+ dassert(nSurf < kSurfMax);
+ if (pVectorData->at1d[nSurf].at0 >= 0)
+ {
+ spritetype *pFX = gFX.fxSpawn(pVectorData->at1d[nSurf].at0, nSector, x, y, z, 0);
+ if (pFX)
+ {
+ pFX->ang = (GetWallAngle(nWall)+512)&2047;
+ pFX->cstat |= 16;
+ }
+ }
+ }
+ break;
+ }
+ case 4:
+ {
+ int nWall = gHitInfo.hitwall;
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ nSurf = surfType[wall[nWall].overpicnum];
+ int nXWall = wall[nWall].extra;
+ if (nXWall > 0)
+ {
+ XWALL *pXWall = &xwall[nXWall];
+ if (pXWall->triggerVector)
+ trTriggerWall(nWall, pXWall, 51);
+ }
+ break;
+ }
+ case 3:
+ {
+ int nSprite = gHitInfo.hitsprite;
+ nSurf = surfType[sprite[nSprite].picnum];
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ x -= mulscale(a4, 112, 14);
+ y -= mulscale(a5, 112, 14);
+ z -= mulscale(a6, 112<<4, 14);
+ int shift = 4;
+ if (vectorType == VECTOR_TYPE_0 && !IsPlayerSprite(pSprite))
+ shift = 3;
+ actDamageSprite(nShooter, pSprite, pVectorData->at0, pVectorData->at1<extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->Vector)
+ trTriggerSprite(nSprite, pXSprite, 31);
+ }
+ if (pSprite->statnum == 4)
+ {
+ int t = thingInfo[pSprite->type-kThingBase].at2;
+ if (t > 0 && pVectorData->at5)
+ {
+ int t2 = divscale(pVectorData->at5, t, 8);
+ xvel[nSprite] += mulscale16(a4, t2);
+ yvel[nSprite] += mulscale16(a5, t2);
+ zvel[nSprite] += mulscale16(a6, t2);
+ }
+ if (pVectorData->at11)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (!pXSprite->burnTime)
+ evPost(nSprite, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(actSpriteIdToOwnerId(nShooter), pXSprite, pVectorData->at11);
+ }
+ }
+ if (pSprite->statnum == 6)
+ {
+ int t = pSprite->type == 426 ? 0 : dudeInfo[pSprite->type-kDudeBase].mass;
+
+ if (IsDudeSprite(pSprite)) {
+ switch (pSprite->lotag) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ t = getDudeMassBySpriteSize(pSprite);
+ break;
+ }
+ }
+
+ if (t > 0 && pVectorData->at5)
+ {
+ int t2 = divscale(pVectorData->at5, t, 8);
+ xvel[nSprite] += mulscale16(a4, t2);
+ yvel[nSprite] += mulscale16(a5, t2);
+ zvel[nSprite] += mulscale16(a6, t2);
+ }
+ if (pVectorData->at11)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (!pXSprite->burnTime)
+ evPost(nSprite, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(actSpriteIdToOwnerId(nShooter), pXSprite, pVectorData->at11);
+ }
+ if (Chance(pVectorData->atd))
+ {
+ int t = gVectorData[19].maxDist;
+ a4 += Random3(4000);
+ a5 += Random3(4000);
+ a6 += Random3(4000);
+ if (HitScan(pSprite, gHitInfo.hitz, a4, a5, a6, CLIPMASK1, t) == 0)
+ {
+ if (approxDist(gHitInfo.hitx-pSprite->x, gHitInfo.hity-pSprite->y) <= t)
+ {
+ int nWall = gHitInfo.hitwall;
+ int nSector = gHitInfo.hitsect;
+ if (actCanSplatWall(nWall))
+ {
+ int x = gHitInfo.hitx - mulscale(a4, 16, 14);
+ int y = gHitInfo.hity - mulscale(a5, 16, 14);
+ int z = gHitInfo.hitz - mulscale(a6, 16<<4, 14);
+ int nSurf = surfType[wall[nWall].picnum];
+ VECTORDATA *pVectorData = &gVectorData[19];
+ FX_ID t2 = pVectorData->at1d[nSurf].at1;
+ FX_ID t3 = pVectorData->at1d[nSurf].at2;
+ spritetype *pFX = NULL;
+ if (t2 > FX_NONE && (t3 == FX_NONE || Chance(0x4000)))
+ pFX = gFX.fxSpawn(t2, nSector, x, y, z, 0);
+ else if(t3 > FX_NONE)
+ pFX = gFX.fxSpawn(t3, nSector, x, y, z, 0);
+ if (pFX)
+ {
+ zvel[pFX->index] = 0x2222;
+ pFX->ang = (GetWallAngle(nWall)+512)&2047;
+ pFX->cstat |= 16;
+ }
+ }
+ }
+ }
+ }
+ for (int i = 0; i < pVectorData->at15; i++)
+ if (Chance(pVectorData->at19))
+ fxSpawnBlood(pSprite, pVectorData->at1<<4);
+ }
+ break;
+ }
+ }
+ }
+ dassert(nSurf < kSurfMax);
+ if (pVectorData->at1d[nSurf].at1 >= 0)
+ gFX.fxSpawn(pVectorData->at1d[nSurf].at1, nSector, x, y, z, 0);
+ if (pVectorData->at1d[nSurf].at2 >= 0)
+ gFX.fxSpawn(pVectorData->at1d[nSurf].at2, nSector, x, y, z, 0);
+ if (pVectorData->at1d[nSurf].at3 >= 0)
+ sfxPlay3DSound(x, y, z, pVectorData->at1d[nSurf].at3, nSector);
+}
+
+void FireballSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_11, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ int nFX = pFX->index;
+ xvel[nFX] = xvel[nSprite];
+ yvel[nFX] = yvel[nSprite];
+ zvel[nFX] = zvel[nSprite];
+ }
+}
+
+void NapalmSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_12, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ int nFX = pFX->index;
+ xvel[nFX] = xvel[nSprite];
+ yvel[nFX] = yvel[nSprite];
+ zvel[nFX] = zvel[nSprite];
+ }
+}
+
+void sub_3888C(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ int nFX = pFX->index;
+ xvel[nFX] = xvel[nSprite];
+ yvel[nFX] = yvel[nSprite];
+ zvel[nFX] = zvel[nSprite];
+ }
+}
+
+void sub_38938(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_33, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ int nFX = pFX->index;
+ xvel[nFX] = xvel[nSprite];
+ yvel[nFX] = yvel[nSprite];
+ zvel[nFX] = zvel[nSprite];
+ }
+}
+
+void TreeToGibCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ pSprite->type = 417;
+ pXSprite->state = 1;
+ pXSprite->data1 = 15;
+ pXSprite->data2 = 0;
+ pXSprite->data3 = 0;
+ pXSprite->health = thingInfo[17].at0;
+ pXSprite->data4 = 312;
+ pSprite->cstat |= 257;
+}
+
+void DudeToGibCallback1(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ pSprite->type = 426;
+ pXSprite->data1 = 8;
+ pXSprite->data2 = 0;
+ pXSprite->data3 = 0;
+ pXSprite->health = thingInfo[26].at0;
+ pXSprite->data4 = 319;
+ pXSprite->triggerOnce = 0;
+ pXSprite->isTriggered = 0;
+ pXSprite->locked = 0;
+ pXSprite->targetX = gFrameClock;
+ pXSprite->state = 1;
+}
+
+void DudeToGibCallback2(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ pSprite->type = 426;
+ pXSprite->data1 = 3;
+ pXSprite->data2 = 0;
+ pXSprite->data3 = 0;
+ pXSprite->health = thingInfo[26].at0;
+ pXSprite->data4 = 319;
+ pXSprite->triggerOnce = 0;
+ pXSprite->isTriggered = 0;
+ pXSprite->locked = 0;
+ pXSprite->targetX = gFrameClock;
+ pXSprite->state = 1;
+}
+
+void actPostSprite(int nSprite, int nStatus)
+{
+ int n;
+ dassert(gPostCount < kMaxSprites);
+ dassert(nSprite < kMaxSprites && sprite[nSprite].statnum < kMaxStatus);
+ dassert(nStatus >= 0 && nStatus <= kStatFree);
+ if (sprite[nSprite].hitag&32)
+ {
+ for (n = 0; n < gPostCount; n++)
+ if (gPost[n].at0 == nSprite)
+ break;
+ dassert(n < gPostCount);
+ }
+ else
+ {
+ n = gPostCount;
+ sprite[nSprite].hitag |= 32;
+ gPostCount++;
+ }
+ gPost[n].at0 = nSprite;
+ gPost[n].at2 = nStatus;
+}
+
+void actPostProcess(void)
+{
+ for (int i = 0; i < gPostCount; i++)
+ {
+ POSTPONE *pPost = &gPost[i];
+ int nSprite = pPost->at0;
+ spritetype *pSprite = &sprite[nSprite];
+ pSprite->hitag &= ~32;
+ int nStatus = pPost->at2;
+ if (nStatus == kStatFree)
+ {
+ evKill(nSprite, 3);
+ if (sprite[nSprite].extra > 0)
+ seqKill(3, sprite[nSprite].extra);
+ DeleteSprite(nSprite);
+ }
+ else
+ ChangeSpriteStat(nSprite, nStatus);
+ }
+ gPostCount = 0;
+}
+
+void MakeSplash(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ UNREFERENCED_PARAMETER(pXSprite);
+ pSprite->hitag &= ~2;
+ int nXSprite = pSprite->extra;
+ pSprite->z -= 4<<8;
+ int nSurface = tileGetSurfType(gSpriteHit[nXSprite].florhit);
+ switch (pSprite->type)
+ {
+ case 423:
+ if (nSurface == 5)
+ {
+ seqSpawn(6, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite, 356, -1, 0);
+ }
+ else
+ {
+ seqSpawn(7, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite, 354, -1, 0);
+ }
+ break;
+ case 424:
+ seqSpawn(8, 3, nXSprite, -1);
+ sfxPlay3DSound(pSprite, 354, -1, 0);
+ break;
+ }
+}
+
+class ActorLoadSave : public LoadSave
+{
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+void ActorLoadSave::Load(void)
+{
+ Read(gSpriteHit, sizeof(gSpriteHit));
+ Read(gAffectedSectors, sizeof(gAffectedSectors));
+ Read(gAffectedXWalls, sizeof(gAffectedXWalls));
+ Read(&gPostCount, sizeof(gPostCount));
+ Read(gPost, sizeof(gPost));
+ actInit();
+}
+
+void ActorLoadSave::Save(void)
+{
+ Write(gSpriteHit, sizeof(gSpriteHit));
+ Write(gAffectedSectors, sizeof(gAffectedSectors));
+ Write(gAffectedXWalls, sizeof(gAffectedXWalls));
+ Write(&gPostCount, sizeof(gPostCount));
+ Write(gPost, sizeof(gPost));
+}
+
+static ActorLoadSave *myLoadSave;
+
+void ActorLoadSaveConstruct(void)
+{
+ myLoadSave = new ActorLoadSave();
+}
+
+
+// By NoOne: The following functions required for random event features
+//-------------------------
+
+int GetDataVal(spritetype* pSprite, int data) {
+ XSPRITE* pXSprite = &xsprite[pSprite->extra];
+ int rData[4];
+
+ rData[0] = pXSprite->data1; rData[2] = pXSprite->data3;
+ rData[1] = pXSprite->data2; rData[3] = pXSprite->data4;
+
+ return rData[data];
+}
+
+
+std::default_random_engine rng;
+int my_random(int a, int b)
+{
+
+ std::uniform_int_distribution dist_a_b(a, b);
+ return dist_a_b(rng);
+}
+
+// tries to get random data field of sprite
+int GetRandDataVal(int *rData, spritetype* pSprite) {
+ int temp[4];
+ if (rData != NULL && pSprite != NULL) return -1;
+ else if (pSprite != NULL) {
+
+ if (pSprite->extra < 0)
+ return -1;
+
+ if (rData == NULL)
+ rData = temp;
+
+ XSPRITE* pXSprite = &xsprite[pSprite->extra];
+ rData[0] = pXSprite->data1; rData[2] = pXSprite->data3;
+ rData[1] = pXSprite->data2; rData[3] = pXSprite->data4;
+
+ }
+ else if (rData == NULL) {
+ return -1;
+ }
+
+ int random = 0;
+ // randomize only in case if at least 2 data fields are not empty
+ int a = 1; int b = -1;
+ for (int i = 0; i <= 3; i++) {
+ if (rData[i] == 0) {
+ if (a++ > 2)
+ return -1;
+ }
+ else if (b == -1) {
+ b++;
+ }
+ }
+
+ // try randomize few times
+ int maxRetries = 10;
+ while (maxRetries > 0) {
+
+ // use true random only for single player mode
+ if (gGameOptions.nGameType == 0 && !VanillaMode() && !DemoRecordStatus()) {
+ rng.seed(std::random_device()());
+ random = my_random(0, 4);
+ // otherwise use Blood's default one. In the future it maybe possible to make
+ // host send info to clients about what was generated.
+ } else {
+ random = Random(3);
+ }
+
+ if (rData[random] > 0) return rData[random];
+ maxRetries--;
+ }
+
+ // if nothing, get first found data value from top
+ return rData[b];
+}
+
+// this function drops random item using random pickup generator(s)
+spritetype* DropRandomPickupObject(spritetype* pSprite, short prevItem) {
+ spritetype* pSprite2 = NULL;
+
+ int rData[4]; int selected = -1;
+ rData[0] = xsprite[pSprite->extra].data1; rData[2] = xsprite[pSprite->extra].data3;
+ rData[1] = xsprite[pSprite->extra].data2; rData[3] = xsprite[pSprite->extra].data4;
+
+ // randomize only in case if at least 2 data fields fits.
+ for (int i = 0; i <= 3; i++)
+ if (rData[i] < kWeaponItemBase || rData[i] >= kItemMax)
+ rData[i] = 0;
+
+ int maxRetries = 9;
+ while ((selected = GetRandDataVal(rData, NULL)) == prevItem) if (maxRetries <= 0) break;
+ if (selected > 0) {
+ spritetype* pSource = pSprite; XSPRITE* pXSource = &xsprite[pSource->extra];
+ pSprite2 = actDropObject(pSprite, selected);
+ pXSource->dropMsg = pSprite2->lotag; // store dropped item lotag in dropMsg
+
+ if ((pSource->hitag & kHitagExtBit) != 0)
+ {
+ int nXSprite2 = pSprite2->extra;
+ if (nXSprite2 <= 0)
+ nXSprite2 = dbInsertXSprite(pSprite2->index);
+ XSPRITE *pXSprite2 = &xsprite[nXSprite2];
+
+ // inherit spawn sprite trigger settings, so designer can send command when item picked up.
+ pXSprite2->txID = pXSource->txID;
+ pXSprite2->command = pXSource->command;
+ pXSprite2->triggerOn = pXSource->triggerOn;
+ pXSprite2->triggerOff = pXSource->triggerOff;
+
+ pXSprite2->Pickup = true;
+ }
+ }
+
+ return pSprite2;
+}
+
+// this function spawns random dude using dudeSpawn
+spritetype* spawnRandomDude(spritetype* pSprite) {
+ spritetype* pSprite2 = NULL;
+
+ if (pSprite->extra >= 0) {
+ int rData[4]; int selected = -1;
+ rData[0] = xsprite[pSprite->extra].data1; rData[2] = xsprite[pSprite->extra].data3;
+ rData[1] = xsprite[pSprite->extra].data2; rData[3] = xsprite[pSprite->extra].data4;
+
+ // randomize only in case if at least 2 data fields fits.
+ for (int i = 0; i <= 3; i++)
+ if (rData[i] < kDudeBase || rData[i] >= kDudeMax)
+ rData[i] = 0;
+
+ if ((selected = GetRandDataVal(rData,NULL)) > 0)
+ pSprite2 = actSpawnDude(pSprite, selected, -1, 0);
+ }
+
+ return pSprite2;
+}
+//-------------------------
+
+// By NoOne: this function plays sound predefined in missile info
+bool sfxPlayMissileSound(spritetype* pSprite, int missileId) {
+ MissileType* pMissType = &missileInfo[missileId - kMissileBase];
+ if (Chance(0x4000))
+ sfxPlay3DSound(pSprite, pMissType->fireSound[0], -1, 0);
+ else
+ sfxPlay3DSound(pSprite, pMissType->fireSound[1], -1, 0);
+
+ return true;
+}
+
+// By NoOne: this function plays sound predefined in vector info
+bool sfxPlayVectorSound(spritetype* pSprite, int vectorId) {
+ VECTORDATA* pVectorData = &gVectorData[vectorId];
+ if (Chance(0x4000))
+ sfxPlay3DSound(pSprite, pVectorData->fireSound[0], -1, 0);
+ else
+ sfxPlay3DSound(pSprite, pVectorData->fireSound[1], -1, 0);
+
+ return true;
+}
+
+// By NoOne: this function allows to spawn new custom dude and inherit spawner settings,
+// so custom dude can have different weapons, hp and so on...
+spritetype* actSpawnCustomDude(spritetype* pSprite, int nDist) {
+
+ spritetype* pSource = pSprite; XSPRITE* pXSource = &xsprite[pSource->extra];
+ spritetype* pDude = actSpawnSprite(pSprite,6); XSPRITE* pXDude = &xsprite[pDude->extra];
+
+ int x, y, z = pSprite->z, nAngle = pSprite->ang, nType = kGDXDudeUniversalCultist;
+
+ if (nDist > 0) {
+ x = pSprite->x + mulscale30r(Cos(nAngle), nDist);
+ y = pSprite->y + mulscale30r(Sin(nAngle), nDist);
+ }
+ else {
+ x = pSprite->x;
+ y = pSprite->y;
+ }
+
+ pDude->lotag = nType; pDude->ang = nAngle;
+ vec3_t pos = { x, y, z }; setsprite(pDude->index, &pos);
+ pDude->cstat |= 0x1101; pDude->clipdist = dudeInfo[nType - kDudeBase].clipdist;
+
+ // inherit weapon and sound settings.
+ pXDude->data1 = pXSource->data1;
+ pXDude->data3 = pXSource->data3;
+
+ // inherit movement speed.
+ pXDude->busyTime = pXSource->busyTime;
+
+ // inherit custom hp settings
+ if (pXSource->data4 > 0) pXDude->health = pXSource->data4;
+ else pXDude->health = dudeInfo[nType - kDudeBase].startHealth << 4;
+
+ // inherit seq settings
+ int seqId = dudeInfo[nType - kDudeBase].seqStartID;
+ if (pXSource->data2 > 0) seqId = pXSource->data2;
+ pXDude->data2 = seqId;
+
+ if (gSysRes.Lookup(seqId,"SEQ"))
+ seqSpawn(seqId, 3, pDude->extra, -1);
+
+ if ((pSource->hitag & kHitagExtBit) != 0) {
+ //inherit pal?
+ if (pDude->pal <= 0) pDude->pal = pSource->pal;
+
+ // inherit spawn sprite trigger settings, so designer can count monsters.
+ pXDude->txID = pXSource->txID;
+ pXDude->command = pXSource->command;
+ pXDude->triggerOn = pXSource->triggerOn;
+ pXDude->triggerOff = pXSource->triggerOff;
+
+ // inherit drop items
+ pXDude->dropMsg = pXSource->dropMsg;
+
+ // inherit dude flags
+ pXDude->dudeDeaf = pXSource->dudeDeaf;
+ pXDude->dudeGuard = pXSource->dudeGuard;
+ pXDude->dudeAmbush = pXSource->dudeAmbush;
+ pXDude->dudeFlag4 = pXSource->dudeFlag4;
+ }
+
+ aiInitSprite(pDude);
+ return pDude;
+}
+
+int getDudeMassBySpriteSize(spritetype* pSprite) {
+ int mass = 0; int minMass = 5;
+ if (IsDudeSprite(pSprite)) {
+ int picnum = pSprite->picnum; Seq* pSeq = NULL;
+ int seqStartId = dudeInfo[pSprite->lotag - kDudeBase].seqStartID;
+ switch (pSprite->lotag) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ seqStartId = xsprite[pSprite->extra].data2;
+ break;
+ }
+
+
+ DICTNODE* hSeq = gSysRes.Lookup(seqStartId, "SEQ");
+ pSeq = (Seq*)gSysRes.Load(hSeq);
+ if (pSeq != NULL)
+ picnum = seqGetTile(&pSeq->frames[0]);
+
+ int clipDist = pSprite->clipdist;
+ if (clipDist <= 0)
+ clipDist = dudeInfo[pSprite->lotag - kDudeBase].clipdist;
+
+ int xrepeat = pSprite->xrepeat;
+ int x = tilesiz[picnum].x;
+ if (xrepeat > 64) x += ((xrepeat - 64) * 2);
+ else if (xrepeat < 64) x -= ((64 - xrepeat) * 2);
+
+ int yrepeat = pSprite->yrepeat;
+ int y = tilesiz[picnum].y;
+ if (yrepeat > 64) y += ((yrepeat - 64) * 2);
+ else if (yrepeat < 64) y -= ((64 - yrepeat) * 2);
+
+ mass = ((x + y) * clipDist) / 25;
+ //if ((mass+=(x+y)) > 200) mass+=((mass - 200)*16);
+ }
+
+ if (mass < minMass) return minMass;
+ else if (mass > 65000) return 65000;
+ return mass;
+}
+
+
+bool ceilIsTooLow(spritetype* pSprite) {
+ if (pSprite != NULL) {
+
+ sectortype* pSector = §or[pSprite->sectnum];
+ int a = pSector->ceilingz - pSector->floorz;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int b = top - bottom;
+ if (a > b) return true;
+ }
+
+ return false;
+}
+
diff --git a/source/blood/src/actor.h b/source/blood/src/actor.h
new file mode 100644
index 000000000..70aefa05d
--- /dev/null
+++ b/source/blood/src/actor.h
@@ -0,0 +1,280 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common_game.h"
+#include "blood.h"
+#include "db.h"
+#include "fx.h"
+#include "gameutil.h"
+
+enum DAMAGE_TYPE {
+ DAMAGE_TYPE_0 = 0,
+ DAMAGE_TYPE_1, // Flame
+ DAMAGE_TYPE_2,
+ DAMAGE_TYPE_3,
+ DAMAGE_TYPE_4,
+ DAMAGE_TYPE_5,
+ DAMAGE_TYPE_6, // Tesla
+};
+
+enum VECTOR_TYPE {
+ VECTOR_TYPE_0 = 0,
+ VECTOR_TYPE_1,
+ VECTOR_TYPE_2,
+ VECTOR_TYPE_3,
+ VECTOR_TYPE_4,
+ VECTOR_TYPE_5,
+ VECTOR_TYPE_6,
+ VECTOR_TYPE_7,
+ VECTOR_TYPE_8,
+ VECTOR_TYPE_9,
+ VECTOR_TYPE_10,
+ VECTOR_TYPE_11,
+ VECTOR_TYPE_12,
+ VECTOR_TYPE_13,
+ VECTOR_TYPE_14,
+ VECTOR_TYPE_15,
+ VECTOR_TYPE_16,
+ VECTOR_TYPE_17,
+ VECTOR_TYPE_18,
+ VECTOR_TYPE_19,
+ VECTOR_TYPE_20,
+ VECTOR_TYPE_21,
+ VECTOR_TYPE_22,
+ kVectorMax,
+};
+
+struct THINGINFO
+{
+ short at0; // health
+ short at2; // mass
+ unsigned char at4; // clipdist
+ short at5; // flags
+ int at7; // elasticity
+ int atb; // damage resistance
+ short atf; // cstat
+ short at11; // picnum
+ char at13; // shade
+ unsigned char at14; // pal
+ unsigned char at15; // xrepeat
+ unsigned char at16; // yrepeat
+ int at17[7]; // damage
+ int allowThrow; // By NoOne: indicates if kGDXCustomDude can throw it
+};
+
+struct AMMOITEMDATA
+{
+ short at0;
+ short picnum; // startHealth
+ char shade; // mass
+ char at5;
+ unsigned char xrepeat; // at6
+ unsigned char yrepeat; // at7
+ short at8;
+ unsigned char ata;
+ unsigned char atb;
+};
+
+struct WEAPONITEMDATA
+{
+ short at0;
+ short picnum; // startHealth
+ char shade; // mass
+ char at5;
+ unsigned char xrepeat; // at6
+ unsigned char yrepeat; // at7
+ short at8;
+ short ata;
+ short atc;
+};
+
+struct ITEMDATA
+{
+ short at0; // unused?
+ short picnum; // startHealth
+ char shade; // mass
+ char at5; // unused?
+ unsigned char xrepeat; // at6
+ unsigned char yrepeat; // at7
+ short at8;
+};
+
+struct MissileType
+{
+ short picnum;
+ int at2; // speed
+ int at6; // angle
+ unsigned char ata; // xrepeat
+ unsigned char atb; // yrepeat
+ char atc; // shade
+ unsigned char atd; // clipdist
+ int fireSound[2]; // By NoOne: predefined fire sounds. used by kGDXCustomDude, but can be used for something else.
+};
+
+struct EXPLOSION
+{
+ unsigned char at0;
+ char at1; // dmg
+ char at2; // dmg rnd
+ int at3; // radius
+ int at7;
+ int atb;
+ int atf;
+ int at13;
+ int at17;
+};
+
+struct VECTORDATA_at1d {
+ FX_ID at0;
+ FX_ID at1;
+ FX_ID at2;
+ int at3;
+};
+
+struct VECTORDATA {
+ DAMAGE_TYPE at0;
+ int at1; // damage
+ int at5;
+ int maxDist; // range
+ int atd;
+ int at11; // burn
+ int at15; // blood splats
+ int at19; // blood splat chance
+ VECTORDATA_at1d at1d[15];
+ int fireSound[2]; // By NoOne: predefined fire sounds. used by kGDXCustomDude, but can be used for something else.
+};
+
+struct SPRITEHIT {
+ int hit, ceilhit, florhit;
+};
+
+extern AMMOITEMDATA gAmmoItemData[];
+extern WEAPONITEMDATA gWeaponItemData[];
+extern ITEMDATA gItemData[];
+extern MissileType missileInfo[];
+extern EXPLOSION explodeInfo[];
+extern THINGINFO thingInfo[];
+extern VECTORDATA gVectorData[];
+
+extern SPRITEHIT gSpriteHit[];
+
+extern int gDudeDrag;
+extern short gAffectedSectors[kMaxSectors];
+extern short gAffectedXWalls[kMaxXWalls];
+
+inline void GetSpriteExtents(spritetype *pSprite, int *top, int *bottom)
+{
+ *top = *bottom = pSprite->z;
+ if ((pSprite->cstat & 0x30) != 0x20)
+ {
+ int height = tilesiz[pSprite->picnum].y;
+ int center = height / 2 + picanm[pSprite->picnum].yofs;
+ *top -= (pSprite->yrepeat << 2)*center;
+ *bottom += (pSprite->yrepeat << 2)*(height - center);
+ }
+}
+
+
+inline bool IsPlayerSprite(spritetype *pSprite)
+{
+ if (pSprite->type >= kDudePlayer1 && pSprite->type <= kDudePlayer8)
+ return 1;
+ return 0;
+}
+
+inline bool IsDudeSprite(spritetype *pSprite)
+{
+ if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ return 1;
+ return 0;
+}
+
+inline void actBurnSprite(int nSource, XSPRITE *pXSprite, int nTime)
+{
+ pXSprite->burnTime = ClipHigh(pXSprite->burnTime + nTime, sprite[pXSprite->reference].statnum == 6 ? 2400 : 1200);
+ pXSprite->burnSource = nSource;
+}
+
+bool IsItemSprite(spritetype *pSprite);
+bool IsWeaponSprite(spritetype *pSprite);
+bool IsAmmoSprite(spritetype *pSprite);
+bool IsUnderwaterSector(int nSector);
+int actSpriteOwnerToSpriteId(spritetype *pSprite);
+void actPropagateSpriteOwner(spritetype *pTarget, spritetype *pSource);
+int actSpriteIdToOwnerId(int nSprite);
+int actOwnerIdToSpriteId(int nSprite);
+bool actTypeInSector(int nSector, int nType);
+void actAllocateSpares(void);
+void actInit(void);
+void ConcussSprite(int a1, spritetype *pSprite, int x, int y, int z, int a6);
+int actWallBounceVector(int *x, int *y, int nWall, int a4);
+int actFloorBounceVector(int *x, int *y, int *z, int nSector, int a5);
+void sub_2A620(int nSprite, int x, int y, int z, int nSector, int nDist, int a7, int a8, DAMAGE_TYPE a9, int a10, int a11, int a12, int a13);
+void sub_2AA94(spritetype *pSprite, XSPRITE *pXSprite);
+spritetype *actSpawnFloor(spritetype *pSprite);
+spritetype *actDropAmmo(spritetype *pSprite, int nType);
+spritetype *actDropWeapon(spritetype *pSprite, int nType);
+spritetype *actDropItem(spritetype *pSprite, int nType);
+spritetype *actDropKey(spritetype *pSprite, int nType);
+spritetype *actDropFlag(spritetype *pSprite, int nType);
+spritetype *actDropObject(spritetype *pSprite, int nType);
+bool actHealDude(XSPRITE *pXDude, int a2, int a3);
+void actKillDude(int a1, spritetype *pSprite, DAMAGE_TYPE a3, int a4);
+int actDamageSprite(int nSource, spritetype *pSprite, DAMAGE_TYPE a3, int a4);
+void actHitcodeToData(int a1, HITINFO *pHitInfo, int *a3, spritetype **a4, XSPRITE **a5, int *a6, walltype **a7, XWALL **a8, int *a9, sectortype **a10, XSECTOR **a11);
+void actImpactMissile(spritetype *pMissile, int a2);
+void actKickObject(spritetype *pSprite1, spritetype *pSprite2);
+void actTouchFloor(spritetype *pSprite, int nSector);
+void ProcessTouchObjects(spritetype *pSprite, int nXSprite);
+void actAirDrag(spritetype *pSprite, int a2);
+int MoveThing(spritetype *pSprite);
+void MoveDude(spritetype *pSprite);
+int MoveMissile(spritetype *pSprite);
+void actExplodeSprite(spritetype *pSprite);
+void actActivateGibObject(spritetype *pSprite, XSPRITE *pXSprite);
+bool IsUnderWater(spritetype *pSprite);
+void actProcessSprites(void);
+spritetype * actSpawnSprite(int nSector, int x, int y, int z, int nStat, char a6);
+spritetype *actSpawnDude(spritetype *pSource, short nType, int a3, int a4);
+spritetype * actSpawnSprite(spritetype *pSource, int nStat);
+spritetype * actSpawnThing(int nSector, int x, int y, int z, int nThingType);
+spritetype * actFireThing(spritetype *pSprite, int a2, int a3, int a4, int thingType, int a6);
+spritetype* actFireMissile(spritetype *pSprite, int a2, int a3, int a4, int a5, int a6, int nType);
+int actGetRespawnTime(spritetype *pSprite);
+bool actCheckRespawn(spritetype *pSprite);
+bool actCanSplatWall(int nWall);
+void actFireVector(spritetype *pShooter, int a2, int a3, int a4, int a5, int a6, VECTOR_TYPE vectorType);
+void actPostSprite(int nSprite, int nStatus);
+void actPostProcess(void);
+void MakeSplash(spritetype *pSprite, XSPRITE *pXSprite);
+spritetype* DropRandomPickupObject(spritetype* pSprite, short prevItem);
+spritetype* spawnRandomDude(spritetype* pSprite);
+int GetDataVal(spritetype* pSprite, int data);
+int my_random(int a, int b);
+int GetRandDataVal(int *rData, spritetype* pSprite);
+bool sfxPlayMissileSound(spritetype* pSprite, int missileId);
+bool sfxPlayVectorSound(spritetype* pSprite, int vectorId);
+spritetype* actSpawnCustomDude(spritetype* pSprite, int nDist);
+int getDudeMassBySpriteSize(spritetype* pSprite);
+bool ceilIsTooLow(spritetype* pSprite);
\ No newline at end of file
diff --git a/source/blood/src/ai.cpp b/source/blood/src/ai.cpp
new file mode 100644
index 000000000..974c25008
--- /dev/null
+++ b/source/blood/src/ai.cpp
@@ -0,0 +1,1802 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+#include "actor.h"
+#include "ai.h"
+#include "aibat.h"
+#include "aibeast.h"
+#include "aiboneel.h"
+#include "aiburn.h"
+#include "aicaleb.h"
+#include "aicerber.h"
+#include "aicult.h"
+#include "aigarg.h"
+#include "aighost.h"
+#include "aigilbst.h"
+#include "aihand.h"
+#include "aihound.h"
+#include "aiinnoc.h"
+#include "aipod.h"
+#include "airat.h"
+#include "aispid.h"
+#include "aitchern.h"
+#include "aizomba.h"
+#include "aizombf.h"
+#include "aiunicult.h" // By NoOne: add custom dude
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gameutil.h"
+#include "gib.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "player.h"
+#include "seq.h"
+#include "sound.h"
+#include "sfx.h"
+#include "trig.h"
+#include "triggers.h"
+
+int cumulDamage[kMaxXSprites];
+int gDudeSlope[kMaxXSprites];
+DUDEEXTRA gDudeExtra[kMaxXSprites];
+
+AISTATE genIdle = {kAiStateGenIdle, 0, -1, 0, NULL, NULL, NULL, NULL };
+AISTATE genRecoil = {kAiStateRecoil, 5, -1, 20, NULL, NULL, NULL, &genIdle };
+
+int dword_138BB0[5] = {0x2000, 0x4000, 0x8000, 0xa000, 0xe000};
+
+void aiSetGenIdleState(spritetype* pSprite, XSPRITE* pXSprite) {
+ aiNewState(pSprite, pXSprite, &genIdle);
+}
+
+bool sub_5BDA8(spritetype *pSprite, int nSeq)
+{
+ if (pSprite->statnum == 6 && pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ {
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ if (seqGetID(3, pSprite->extra) == pDudeInfo->seqStartID + nSeq && seqGetStatus(3, pSprite->extra) >= 0)
+ return true;
+ }
+ return false;
+}
+
+void aiPlay3DSound(spritetype *pSprite, int a2, AI_SFX_PRIORITY a3, int a4)
+{
+ DUDEEXTRA *pDudeExtra = &gDudeExtra[pSprite->extra];
+ if (a3 == AI_SFX_PRIORITY_0)
+ sfxPlay3DSound(pSprite, a2, a4, 2);
+ else if (a3 > pDudeExtra->at5 || pDudeExtra->at0 <= gFrameClock)
+ {
+ sfxKill3DSound(pSprite, -1, -1);
+ sfxPlay3DSound(pSprite, a2, a4, 0);
+ pDudeExtra->at5 = a3;
+ pDudeExtra->at0 = gFrameClock+120;
+ }
+}
+
+void aiNewState(spritetype *pSprite, XSPRITE *pXSprite, AISTATE *pAIState)
+{
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ pXSprite->stateTimer = pAIState->at8;
+ pXSprite->aiState = pAIState;
+ int seqStartId = pDudeInfo->seqStartID;
+
+ if (pAIState->at0 >= 0) {
+ // By NoOne: Custom dude uses data2 to keep it's seqStartId
+ switch (pSprite->type) {
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ seqStartId = pXSprite->data2;
+ break;
+ }
+ seqStartId += pAIState->at0;
+ if (gSysRes.Lookup(seqStartId, "SEQ"))
+ seqSpawn(seqStartId, 3, pSprite->extra, pAIState->at4);
+ }
+
+ if (pAIState->atc) pAIState->atc(pSprite, pXSprite); // entry function
+}
+bool dudeIsImmune(spritetype* pSprite, int dmgType) {
+ if (dmgType < 0 || dmgType > 6) return true;
+ else if (dudeInfo[pSprite->type - kDudeBase].startDamage[dmgType] == 0) return true;
+ else if (pSprite->extra >= 0 && xsprite[pSprite->extra].locked == 1) return true; // if dude is locked, it immune to any dmg.
+ return false;
+}
+bool CanMove(spritetype *pSprite, int a2, int nAngle, int nRange)
+{
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ HitScan(pSprite, z, Cos(nAngle)>>16, Sin(nAngle)>>16, 0, CLIPMASK0, nRange);
+ int nDist = approxDist(x-gHitInfo.hitx, y-gHitInfo.hity);
+ if (nDist - (pSprite->clipdist << 2) < nRange)
+ {
+ if (gHitInfo.hitsprite < 0 || a2 != gHitInfo.hitsprite)
+ return false;
+ return true;
+ }
+ x += mulscale30(nRange, Cos(nAngle));
+ y += mulscale30(nRange, Sin(nAngle));
+ int nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ if (!FindSector(x, y, z, &nSector))
+ return false;
+ int floorZ = getflorzofslope(nSector, x, y);
+ int UNUSED(ceilZ) = getceilzofslope(nSector, x, y);
+ int nXSector = sector[nSector].extra;
+ char Underwater = 0; char Water = 0; char Depth = 0; char Crusher = 0;
+ XSECTOR* pXSector = NULL;
+ if (nXSector > 0)
+ {
+ pXSector = &xsector[nXSector];
+ if (pXSector->Underwater)
+ Underwater = 1;
+ if (pXSector->Depth)
+ Depth = 1;
+ if (sector[nSector].lotag == kSecDamage || pXSector->damageType > 0) {
+ // By NoOne: a quick fix for Cerberus spinning in E3M7-like maps, where damage sectors is used.
+ // It makes ignore danger if enemy immune to N damageType. As result Cerberus start acting like
+ // in Blood 1.0 so it can move normally to player. It's up to you for adding rest of enemies here as
+ // i don't think it will broke something in game.
+ switch (pSprite->type) {
+ case 227: // Cerberus
+ case 228: // 1 Head Cerberus
+ if (VanillaMode() || !dudeIsImmune(pSprite, pXSector->damageType))
+ Crusher = 1;
+ break;
+ default:
+ Crusher = 1;
+ break;
+ }
+ }
+ }
+ int nUpper = gUpperLink[nSector];
+ int nLower = gLowerLink[nSector];
+ if (nUpper >= 0)
+ {
+ if (sprite[nUpper].type == kMarkerUpWater || sprite[nUpper].type == kMarkerUpGoo)
+ Water = Depth = 1;
+ }
+ if (nLower >= 0)
+ {
+ if (sprite[nLower].type == kMarkerLowWater || sprite[nLower].type == kMarkerLowGoo)
+ Depth = 1;
+ }
+ switch (pSprite->type)
+ {
+ case 206:
+ case 207:
+ case 219:
+ if (pSprite->clipdist > nDist)
+ return 0;
+ if (Depth)
+ {
+ // Ouch...
+ if (Depth)
+ return false;
+ if (Crusher)
+ return false;
+ }
+ break;
+ case 218:
+ if (Water)
+ return false;
+ if (!Underwater)
+ return false;
+ if (Underwater)
+ return true;
+ break;
+ case 204:
+ case 213:
+ case 214:
+ case 215:
+ case 216:
+ case 211:
+ case 220:
+ case 227:
+ case 245:
+ if (Crusher)
+ return false;
+ if (Depth || Underwater)
+ return false;
+ if (floorZ - bottom > 0x2000)
+ return false;
+ break;
+ case 203:
+ case 210:
+ case 217:
+ case kGDXDudeUniversalCultist:
+ case kGDXGenDudeBurning:
+ if ((Crusher && !dudeIsImmune(pSprite, pXSector->damageType)) || xsprite[pSprite->extra].dudeGuard) return false;
+ return true;
+ default:
+ if (Crusher)
+ return false;
+ if ((nXSector < 0 || (!xsector[nXSector].Underwater && !xsector[nXSector].Depth)) && floorZ - bottom > 0x2000)
+ return false;
+ break;
+ }
+ return 1;
+}
+
+void aiChooseDirection(spritetype *pSprite, XSPRITE *pXSprite, int a3)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ int vc = ((a3+1024-pSprite->ang)&2047)-1024;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int UNUSED(t2) = dmulscale30(dx, nSin, -dy, nCos);
+ int vsi = ((t1*15)>>12) / 2;
+ int v8 = 341;
+ if (vc < 0)
+ v8 = -341;
+ if (CanMove(pSprite, pXSprite->target, pSprite->ang+vc, vsi))
+ pXSprite->goalAng = pSprite->ang+vc;
+ else if (CanMove(pSprite, pXSprite->target, pSprite->ang+vc/2, vsi))
+ pXSprite->goalAng = pSprite->ang+vc/2;
+ else if (CanMove(pSprite, pXSprite->target, pSprite->ang-vc/2, vsi))
+ pXSprite->goalAng = pSprite->ang-vc/2;
+ else if (CanMove(pSprite, pXSprite->target, pSprite->ang+v8, vsi))
+ pXSprite->goalAng = pSprite->ang+v8;
+ else if (CanMove(pSprite, pXSprite->target, pSprite->ang, vsi))
+ pXSprite->goalAng = pSprite->ang;
+ else if (CanMove(pSprite, pXSprite->target, pSprite->ang-v8, vsi))
+ pXSprite->goalAng = pSprite->ang-v8;
+ else if (pSprite->hitag&2)
+ pXSprite->goalAng = pSprite->ang+341;
+ else // Weird..
+ pXSprite->goalAng = pSprite->ang+341;
+ if (Chance(0x8000))
+ pXSprite->dodgeDir = 1;
+ else
+ pXSprite->dodgeDir = -1;
+ if (!CanMove(pSprite, pXSprite->target, pSprite->ang+pXSprite->dodgeDir*512, 512))
+ {
+ pXSprite->dodgeDir = -pXSprite->dodgeDir;
+ if (!CanMove(pSprite, pXSprite->target, pSprite->ang+pXSprite->dodgeDir*512, 512))
+ pXSprite->dodgeDir = 0;
+ }
+}
+
+void aiMoveForward(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (klabs(nAng) > 341)
+ return;
+ xvel[nSprite] += mulscale30(pDudeInfo->frontSpeed, Cos(pSprite->ang));
+ yvel[nSprite] += mulscale30(pDudeInfo->frontSpeed, Sin(pSprite->ang));
+}
+
+void aiMoveTurn(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+}
+
+void aiMoveDodge(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (pXSprite->dodgeDir)
+ {
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ }
+}
+
+void aiActivateDude(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ if (!pXSprite->state)
+ {
+ aiChooseDirection(pSprite, pXSprite, getangle(pXSprite->targetX-pSprite->x, pXSprite->targetY-pSprite->y));
+ pXSprite->state = 1;
+ }
+ switch (pSprite->type)
+ {
+ case 210:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &ghostSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 1600, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &ghostChase);
+ }
+ break;
+ }
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ case 249:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ if (Chance(0x8000))
+ {
+ if (pSprite->type == 201)
+ aiPlay3DSound(pSprite, 4008+Random(5), AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1008+Random(5), AI_SFX_PRIORITY_1, -1);
+ }
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimSearch);
+ break;
+ }
+ }
+ else
+ {
+ if (Chance(0x8000))
+ {
+ if (pSprite->type == 201)
+ aiPlay3DSound(pSprite, 4003+Random(4), AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1003+Random(4), AI_SFX_PRIORITY_1, -1);
+ }
+ switch (pXSprite->medium)
+ {
+ case 0:
+ if (pSprite->type == 201)
+ aiNewState(pSprite, pXSprite, &fanaticChase);
+ else
+ aiNewState(pSprite, pXSprite, &cultistChase);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimChase);
+ break;
+ }
+ }
+ break;
+ }
+
+ case kGDXDudeUniversalCultist:
+ {
+ DUDEEXTRA_at6_u1* pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1) {
+ if (spriteIsUnderwater(pSprite, false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchW);
+ else {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchL);
+ if (Chance(0x4000))
+ sfxPlayGDXGenDudeSound(pSprite, 0, pXSprite->data3);
+ }
+ }
+ else {
+ if (Chance(0x4000))
+ sfxPlayGDXGenDudeSound(pSprite, 0, pXSprite->data3);
+
+ if (spriteIsUnderwater(pSprite, false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseW);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+
+ }
+ break;
+ }
+ case kGDXGenDudeBurning:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnSearch);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnChase);
+ break;
+ case 230:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ pSprite->type = 201;
+ if (pXSprite->target == -1)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ if (Chance(0x8000))
+ aiPlay3DSound(pSprite, 4008+Random(5), AI_SFX_PRIORITY_1, -1);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimSearch);
+ break;
+ }
+ }
+ else
+ {
+ if (Chance(0x8000))
+ aiPlay3DSound(pSprite, 4008+Random(5), AI_SFX_PRIORITY_1, -1);
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistProneChase);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimChase);
+ break;
+ }
+ }
+ break;
+ }
+ case 246:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ pSprite->type = 202;
+ if (pXSprite->target == -1)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ if (Chance(0x8000))
+ aiPlay3DSound(pSprite, 1008+Random(5), AI_SFX_PRIORITY_1, -1);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimSearch);
+ break;
+ }
+ }
+ else
+ {
+ if (Chance(0x8000))
+ aiPlay3DSound(pSprite, 1003+Random(4), AI_SFX_PRIORITY_1, -1);
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistProneChase);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimChase);
+ break;
+ }
+ }
+ break;
+ }
+ case 240:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &cultistBurnSearch);
+ else
+ aiNewState(pSprite, pXSprite, &cultistBurnChase);
+ break;
+ case 219:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (!pSprite->hitag)
+ pSprite->hitag = 9;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &batSearch);
+ else
+ {
+ if (Chance(0xa000))
+ aiPlay3DSound(pSprite, 2000, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &batChase);
+ }
+ break;
+ }
+ case 218:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ else
+ {
+ if (Chance(0x8000))
+ aiPlay3DSound(pSprite, 1501, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1500, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &eelChase);
+ }
+ break;
+ }
+ case 217:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ XSECTOR *pXSector = NULL;
+ if (sector[pSprite->sectnum].extra > 0)
+ pXSector = &xsector[sector[pSprite->sectnum].extra];
+ pDudeExtraE->at0 = 0;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 1;
+ if (pXSprite->target == -1)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastSearch);
+ }
+ else
+ {
+ if (Chance(0x4000))
+ aiPlay3DSound(pSprite, 1701, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1700, AI_SFX_PRIORITY_1, -1);
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimChase);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastChase);
+ }
+ break;
+ }
+ case 203:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ pDudeExtraE->at4 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ else
+ {
+ if (Chance(0xa000))
+ {
+ switch (Random(3))
+ {
+ default:
+ case 0:
+ case 3:
+ aiPlay3DSound(pSprite, 1103, AI_SFX_PRIORITY_1, -1);
+ break;
+ case 1:
+ aiPlay3DSound(pSprite, 1104, AI_SFX_PRIORITY_1, -1);
+ break;
+ case 2:
+ aiPlay3DSound(pSprite, 1105, AI_SFX_PRIORITY_1, -1);
+ break;
+ }
+ }
+ aiNewState(pSprite, pXSprite, &zombieAChase);
+ }
+ break;
+ }
+ case 205:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ pDudeExtraE->at4 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->aiState == &zombieEIdle)
+ aiNewState(pSprite, pXSprite, &zombieEUp);
+ break;
+ }
+ case 244:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ pDudeExtraE->at4 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->aiState == &zombieSIdle)
+ aiNewState(pSprite, pXSprite, &zombie13AC2C);
+ break;
+ }
+ case 204:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ pDudeExtraE->at4 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ else
+ {
+ if (Chance(0x4000))
+ aiPlay3DSound(pSprite, 1201, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1200, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &zombieFChase);
+ }
+ break;
+ }
+ case 241:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &zombieABurnSearch);
+ else
+ aiNewState(pSprite, pXSprite, &zombieABurnChase);
+ break;
+ case 242:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &zombieFBurnSearch);
+ else
+ aiNewState(pSprite, pXSprite, &zombieFBurnChase);
+ break;
+ case 206:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ else
+ {
+ if (Chance(0x4000))
+ aiPlay3DSound(pSprite, 1401, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1400, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &gargoyleFChase);
+ }
+ break;
+ }
+ case 207:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ else
+ {
+ if (Chance(0x4000))
+ aiPlay3DSound(pSprite, 1451, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1450, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &gargoyleFChase);
+ }
+ break;
+ }
+ case 208:
+ case 209:
+ // By NoOne: play gargoyle statue breaking animation if data1 = 1.
+ if (pXSprite->data1 == 1) {
+ if (pSprite->type == 208)
+ aiNewState(pSprite, pXSprite, &statueFBreakSEQ);
+ else
+ aiNewState(pSprite, pXSprite, &statueSBreakSEQ);
+ } else {
+ if (Chance(0x4000)) aiPlay3DSound(pSprite, 1401, AI_SFX_PRIORITY_1, -1);
+ else aiPlay3DSound(pSprite, 1400, AI_SFX_PRIORITY_1, -1);
+
+ if (pSprite->type == 208) aiNewState(pSprite, pXSprite, &gargoyleFMorph);
+ else aiNewState(pSprite, pXSprite, &gargoyleSMorph);
+ }
+ break;
+ case 227:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &cerberusSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 2300, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &cerberusChase);
+ }
+ break;
+ case 228:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &cerberus2Search);
+ else
+ {
+ aiPlay3DSound(pSprite, 2300, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &cerberus2Chase);
+ }
+ break;
+ case 211:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 1300, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &houndChase);
+ }
+ break;
+ case 212:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &handSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 1900, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &handChase);
+ }
+ break;
+ case 220:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 2100, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &ratChase);
+ }
+ break;
+ case 245:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &innocentSearch);
+ else
+ {
+ if (pXSprite->health > 0)
+ aiPlay3DSound(pSprite, 7000+Random(6), AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &innocentChase);
+ }
+ break;
+ case 229:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &tchernobogSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 2350+Random(7), AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &tchernobogChase);
+ }
+ break;
+ case 213:
+ case 214:
+ case 215:
+ pSprite->hitag |= 2;
+ pSprite->cstat &= ~8;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 1800, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &spidChase);
+ }
+ break;
+ case 216:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 1;
+ pDudeExtraE->at0 = 0;
+ pSprite->hitag |= 2;
+ pSprite->cstat &= ~8;
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 1853+Random(1), AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &spidChase);
+ }
+ break;
+ }
+ case 250:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ pDudeExtraE->at4 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &tinycalebSearch);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ break;
+ }
+ }
+ else
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &tinycalebChase);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &tinycalebSwimChase);
+ break;
+ }
+ }
+ break;
+ }
+ case 251:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ pDudeExtraE->at4 = 1;
+ pDudeExtraE->at0 = 0;
+ if (pXSprite->target == -1)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &beastSearch);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ break;
+ }
+ }
+ else
+ {
+ aiPlay3DSound(pSprite, 9009+Random(2), AI_SFX_PRIORITY_1, -1);
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &beastChase);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &beastSwimChase);
+ break;
+ }
+ }
+ break;
+ }
+ case 221:
+ case 223:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &podSearch);
+ else
+ {
+ if (pSprite->type == 223)
+ aiPlay3DSound(pSprite, 2453, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 2473, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &podChase);
+ }
+ break;
+ case 222:
+ case 224:
+ if (pXSprite->target == -1)
+ aiNewState(pSprite, pXSprite, &tentacleSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 2503, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &tentacleChase);
+ }
+ break;
+ }
+}
+
+void aiSetTarget(XSPRITE *pXSprite, int x, int y, int z)
+{
+ pXSprite->target = -1;
+ pXSprite->targetX = x;
+ pXSprite->targetY = y;
+ pXSprite->targetZ = z;
+}
+
+void aiSetTarget(XSPRITE *pXSprite, int nTarget)
+{
+ dassert(nTarget >= 0 && nTarget < kMaxSprites);
+ spritetype *pTarget = &sprite[nTarget];
+ if (pTarget->type >= kDudeBase && pTarget->type < kDudeMax)
+ {
+ if (actSpriteOwnerToSpriteId(&sprite[pXSprite->reference]) != nTarget)
+ {
+ pXSprite->target = nTarget;
+ DUDEINFO *pDudeInfo = &dudeInfo[pTarget->type-kDudeBase];
+ pXSprite->targetX = pTarget->x;
+ pXSprite->targetY = pTarget->y;
+ pXSprite->targetZ = pTarget->z-((pDudeInfo->eyeHeight*pTarget->yrepeat)<<2);
+ }
+ }
+}
+
+
+int aiDamageSprite(spritetype *pSprite, XSPRITE *pXSprite, int nSource, DAMAGE_TYPE nDmgType, int nDamage)
+{
+ dassert(nSource < kMaxSprites);
+ if (!pXSprite->health)
+ return 0;
+ pXSprite->health = ClipLow(pXSprite->health - nDamage, 0);
+ cumulDamage[pSprite->extra] += nDamage;
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int nSprite = pXSprite->reference;
+ if (nSource >= 0)
+ {
+ spritetype *pSource = &sprite[nSource];
+ if (pSprite == pSource)
+ return 0;
+ if (pXSprite->target == -1 || (nSource != pXSprite->target && Chance(pSprite->type == pSource->type ? nDamage*pDudeInfo->changeTargetKin : nDamage*pDudeInfo->changeTarget)))
+ {
+ aiSetTarget(pXSprite, nSource);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ if (nDmgType == DAMAGE_TYPE_6)
+ {
+ DUDEEXTRA *pDudeExtra = &gDudeExtra[pSprite->extra];
+ pDudeExtra->at4 = 1;
+ }
+ switch (pSprite->type)
+ {
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ if (nDmgType != DAMAGE_TYPE_1)
+ {
+ if (!sub_5BDA8(pSprite, 14) && !pXSprite->medium)
+ aiNewState(pSprite, pXSprite, &cultistDodge);
+ else if (sub_5BDA8(pSprite, 14) && !pXSprite->medium)
+ aiNewState(pSprite, pXSprite, &cultistProneDodge);
+ else if (sub_5BDA8(pSprite, 13) && (pXSprite->medium == 1 || pXSprite->medium == 2))
+ aiNewState(pSprite, pXSprite, &cultistSwimDodge);
+ }
+ else if (nDmgType == DAMAGE_TYPE_1 && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
+ {
+ pSprite->type = 240;
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1);
+ aiPlay3DSound(pSprite, 1031+Random(2), AI_SFX_PRIORITY_2, -1);
+ gDudeExtra[pSprite->extra].at0 = gFrameClock+360;
+ actHealDude(pXSprite, dudeInfo[40].startHealth, dudeInfo[40].startHealth);
+ evKill(nSprite, 3, CALLBACK_ID_0);
+ }
+ break;
+ case 245: // innocent
+ if (nDmgType == DAMAGE_TYPE_1 && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
+ {
+ pSprite->type = 239;
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1);
+ gDudeExtra[pSprite->extra].at0 = gFrameClock+360;
+ actHealDude(pXSprite, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
+ evKill(nSprite, 3, CALLBACK_ID_0);
+ }
+ break;
+ case 240:
+ if (Chance(0x4000) && gDudeExtra[pSprite->extra].at0 < gFrameClock)
+ {
+ aiPlay3DSound(pSprite, 1031+Random(2), AI_SFX_PRIORITY_2, -1);
+ gDudeExtra[pSprite->extra].at0 = gFrameClock+360;
+ }
+ if (Chance(0x600) && (pXSprite->medium == 1 || pXSprite->medium == 2))
+ {
+ pSprite->type = 201;
+ pXSprite->burnTime = 0;
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ }
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ {
+ pSprite->type = 202;
+ pXSprite->burnTime = 0;
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ }
+ break;
+ case 206:
+ aiNewState(pSprite, pXSprite, &gargoyleFChase);
+ break;
+ case 204:
+ if (nDmgType == DAMAGE_TYPE_1 && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth)
+ {
+ aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1);
+ aiPlay3DSound(pSprite, 1202, AI_SFX_PRIORITY_2, -1);
+ pSprite->type = 242;
+ aiNewState(pSprite, pXSprite, &zombieFBurnGoto);
+ actHealDude(pXSprite, dudeInfo[42].startHealth, dudeInfo[42].startHealth);
+ evKill(nSprite, 3, CALLBACK_ID_0);
+ }
+ break;
+ case 250: // tiny Caleb
+ if (nDmgType == DAMAGE_TYPE_1 && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth/* && (pXSprite->at17_6 != 1 || pXSprite->at17_6 != 2)*/)
+ {
+ pSprite->type = 239;
+ if (!VanillaMode())
+ pXSprite->scale = -4; // need to change this to 64 later
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1);
+ gDudeExtra[pSprite->extra].at0 = gFrameClock+360;
+ actHealDude(pXSprite, dudeInfo[39].startHealth, dudeInfo[39].startHealth);
+ evKill(nSprite, 3, CALLBACK_ID_0);
+ }
+ break;
+ case kGDXGenDudeBurning:
+ if (Chance(0x2000) && gDudeExtra[pSprite->extra].at0 < gFrameClock) {
+ sfxPlayGDXGenDudeSound(pSprite, 3, pXSprite->data3);
+ gDudeExtra[pSprite->extra].at0 = gFrameClock + 360;
+ }
+ if (pXSprite->burnTime == 0) pXSprite->burnTime = 2400;
+ if (spriteIsUnderwater(pSprite, false)) {
+ pSprite->type = kGDXDudeUniversalCultist;
+ pXSprite->burnTime = 0;
+ pXSprite->health = 1; // so it can be killed with flame weapons while underwater and if already was burning dude before.
+ aiNewState(pSprite, pXSprite, &GDXGenDudeGotoW);
+ }
+ break;
+ case kGDXDudeUniversalCultist:
+ {
+ if (nDmgType == DAMAGE_TYPE_1) {
+ if (pXSprite->health <= pDudeInfo->fleeHealth) {
+ if (getNextIncarnation(pXSprite) == NULL) {
+ removeDudeStuff(pSprite);
+
+ if (pXSprite->data1 >= 459 && pXSprite->data1 < (459 + kExplodeMax) - 1)
+ doExplosion(pSprite, pXSprite->data1 - 459);
+
+ if (spriteIsUnderwater(pSprite, false)) {
+ pXSprite->health = 0;
+ break;
+ }
+
+ if (pXSprite->burnTime <= 0)
+ pXSprite->burnTime = 1200;
+
+ if ((gSysRes.Lookup(pXSprite->data2 + 15, "SEQ") || gSysRes.Lookup(pXSprite->data2 + 16, "SEQ"))
+ && gSysRes.Lookup(pXSprite->data2 + 3, "SEQ")) {
+
+ aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1);
+ sfxPlayGDXGenDudeSound(pSprite, 3, pXSprite->data3);
+ pSprite->type = kGDXGenDudeBurning;
+
+ if (pXSprite->data2 == 11520) // don't inherit palette for burning if using default animation
+ pSprite->pal = 0;
+
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnGoto);
+ actHealDude(pXSprite, dudeInfo[55].startHealth, dudeInfo[55].startHealth);
+ gDudeExtra[pSprite->extra].at0 = gFrameClock + 360;
+ evKill(nSprite, 3, CALLBACK_ID_0);
+
+ }
+
+ } else {
+ actKillDude(nSource, pSprite, nDmgType, nDamage);
+ }
+ }
+ } else if (pXSprite->aiState != &GDXGenDudeDodgeDmgD && pXSprite->aiState != &GDXGenDudeDodgeDmgL
+ && pXSprite->aiState != &GDXGenDudeDodgeDmgW) {
+
+ if (Chance(getDodgeChance(pSprite))) {
+ if (!spriteIsUnderwater(pSprite, false)) {
+ if (!sub_5BDA8(pSprite, 14)) aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeDmgL);
+ else aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeDmgD);
+ }
+ else if (sub_5BDA8(pSprite, 13) && spriteIsUnderwater(pSprite, false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeDmgW);
+ }
+ }
+
+ break;
+ }
+ case 249:
+ if (pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth)
+ {
+ pSprite->type = 251;
+ aiPlay3DSound(pSprite, 9008, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &beastMorphFromCultist);
+ actHealDude(pXSprite, dudeInfo[51].startHealth, dudeInfo[51].startHealth);
+ }
+ break;
+ case 203:
+ case 205:
+ if (nDmgType == DAMAGE_TYPE_1 && pXSprite->health <= (unsigned int)pDudeInfo->fleeHealth)
+ {
+ aiPlay3DSound(pSprite, 361, AI_SFX_PRIORITY_0, -1);
+ aiPlay3DSound(pSprite, 1106, AI_SFX_PRIORITY_2, -1);
+ pSprite->type = 241;
+ aiNewState(pSprite, pXSprite, &zombieABurnGoto);
+ actHealDude(pXSprite, dudeInfo[41].startHealth, dudeInfo[41].startHealth);
+ evKill(nSprite, 3, CALLBACK_ID_0);
+ }
+ break;
+ }
+ }
+ return nDamage;
+}
+
+void RecoilDude(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ char v4 = Chance(0x8000);
+ DUDEEXTRA *pDudeExtra = &gDudeExtra[pSprite->extra];
+ if (pSprite->statnum == 6 && (pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ {
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ switch (pSprite->type)
+ {
+ case kGDXDudeUniversalCultist:
+ {
+ int mass = getDudeMassBySpriteSize(pSprite); int chance3 = getRecoilChance(pSprite);
+ if ((mass < 155 && !spriteIsUnderwater(pSprite, false) && pDudeExtra->at4) || (mass > 155 && Chance(chance3)))
+ {
+ sfxPlayGDXGenDudeSound(pSprite, 1, pXSprite->data3);
+
+ if (gSysRes.Lookup(pXSprite->data2 + 4, "SEQ")) aiNewState(pSprite, pXSprite, &GDXGenDudeRTesla);
+ else if (!v4 || (v4 && gGameOptions.nDifficulty == 0)) aiNewState(pSprite, pXSprite, &GDXGenDudeRecoilD);
+ else if (spriteIsUnderwater(pSprite, false)) aiNewState(pSprite, pXSprite, &GDXGenDudeRecoilW);
+ else aiNewState(pSprite, pXSprite, &GDXGenDudeRecoilL);
+
+ return;
+ }
+
+ if (pXSprite->aiState == &GDXGenDudeDodgeDmgW || pXSprite->aiState == &GDXGenDudeDodgeDmgD
+ || pXSprite->aiState == &GDXGenDudeDodgeDmgL) {
+ if (Chance(chance3)) sfxPlayGDXGenDudeSound(pSprite, 1, pXSprite->data3);
+ return;
+ }
+
+ if ((!dudeIsMelee(pXSprite) && mass < 155) || Chance(chance3)) {
+
+ sfxPlayGDXGenDudeSound(pSprite, 1, pXSprite->data3);
+
+ if (!v4 || (v4 && gGameOptions.nDifficulty == 0)) aiNewState(pSprite, pXSprite, &GDXGenDudeRecoilD);
+ else if (spriteIsUnderwater(pSprite, false)) aiNewState(pSprite, pXSprite, &GDXGenDudeRecoilW);
+ else aiNewState(pSprite, pXSprite, &GDXGenDudeRecoilL);
+
+ }
+
+ break;
+ }
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ case 249:
+ if (pSprite->type == 201)
+ aiPlay3DSound(pSprite, 4013+Random(2), AI_SFX_PRIORITY_2, -1);
+ else
+ aiPlay3DSound(pSprite, 1013+Random(2), AI_SFX_PRIORITY_2, -1);
+ if (!v4 && pXSprite->medium == 0)
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &cultistTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &cultistRecoil);
+ }
+ else if (v4 && pXSprite->medium == 0)
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &cultistTeslaRecoil);
+ else if (gGameOptions.nDifficulty > 0)
+ aiNewState(pSprite, pXSprite, &cultistProneRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &cultistRecoil);
+ }
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSwimRecoil);
+ else
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &cultistTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &cultistRecoil);
+ }
+ break;
+ case 240:
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ break;
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnGoto);
+ break;
+ case 204:
+ aiPlay3DSound(pSprite, 1202, AI_SFX_PRIORITY_2, -1);
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &zombieFTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &zombieFRecoil);
+ break;
+ case 203:
+ case 205:
+ aiPlay3DSound(pSprite, 1106, AI_SFX_PRIORITY_2, -1);
+ if (pDudeExtra->at4 && pXSprite->cumulDamage > pDudeInfo->startHealth/3)
+ aiNewState(pSprite, pXSprite, &zombieATeslaRecoil);
+ else if (pXSprite->cumulDamage > pDudeInfo->startHealth/3)
+ aiNewState(pSprite, pXSprite, &zombieARecoil2);
+ else
+ aiNewState(pSprite, pXSprite, &zombieARecoil);
+ break;
+ case 241:
+ aiPlay3DSound(pSprite, 1106, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &zombieABurnGoto);
+ break;
+ case 242:
+ aiPlay3DSound(pSprite, 1202, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &zombieFBurnGoto);
+ break;
+ case 206:
+ case 207:
+ aiPlay3DSound(pSprite, 1402, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &gargoyleFRecoil);
+ break;
+ case 227:
+ aiPlay3DSound(pSprite, 2302+Random(2), AI_SFX_PRIORITY_2, -1);
+ if (pDudeExtra->at4 && pXSprite->cumulDamage > pDudeInfo->startHealth/3)
+ aiNewState(pSprite, pXSprite, &cerberusTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &cerberusRecoil);
+ break;
+ case 228:
+ aiPlay3DSound(pSprite, 2302+Random(2), AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &cerberus2Recoil);
+ break;
+ case 211:
+ aiPlay3DSound(pSprite, 1302, AI_SFX_PRIORITY_2, -1);
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &houndTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &houndRecoil);
+ break;
+ case 229:
+ aiPlay3DSound(pSprite, 2370+Random(2), AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &tchernobogRecoil);
+ break;
+ case 212:
+ aiPlay3DSound(pSprite, 1902, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &handRecoil);
+ break;
+ case 220:
+ aiPlay3DSound(pSprite, 2102, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &ratRecoil);
+ break;
+ case 219:
+ aiPlay3DSound(pSprite, 2002, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &batRecoil);
+ break;
+ case 218:
+ aiPlay3DSound(pSprite, 1502, AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &eelRecoil);
+ break;
+ case 217:
+ {
+ XSECTOR *pXSector = NULL;
+ if (sector[pSprite->sectnum].extra > 0)
+ pXSector = &xsector[sector[pSprite->sectnum].extra];
+ aiPlay3DSound(pSprite, 1702, AI_SFX_PRIORITY_2, -1);
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastRecoil);
+ break;
+ }
+ case 210:
+ aiPlay3DSound(pSprite, 1602, AI_SFX_PRIORITY_2, -1);
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &ghostTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &ghostRecoil);
+ break;
+ case 213:
+ case 214:
+ case 215:
+ aiPlay3DSound(pSprite, 1802+Random(1), AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &spidDodge);
+ break;
+ case 216:
+ aiPlay3DSound(pSprite, 1851+Random(1), AI_SFX_PRIORITY_2, -1);
+ aiNewState(pSprite, pXSprite, &spidDodge);
+ break;
+ case 245:
+ aiPlay3DSound(pSprite, 7007+Random(2), AI_SFX_PRIORITY_2, -1);
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &innocentTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &innocentRecoil);
+ break;
+ case 250:
+ if (pXSprite->medium == 0)
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &tinycalebTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebRecoil);
+ }
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimRecoil);
+ else
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &tinycalebTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebRecoil);
+ }
+ break;
+ case 251:
+ aiPlay3DSound(pSprite, 9004+Random(2), AI_SFX_PRIORITY_2, -1);
+ if (pXSprite->medium == 0)
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &beastTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &beastRecoil);
+ }
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &beastSwimRecoil);
+ else
+ {
+ if (pDudeExtra->at4)
+ aiNewState(pSprite, pXSprite, &beastTeslaRecoil);
+ else
+ aiNewState(pSprite, pXSprite, &beastRecoil);
+ }
+ break;
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &podRecoil);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacleRecoil);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &genRecoil);
+ break;
+ }
+ pDudeExtra->at4 = 0;
+ }
+}
+
+void aiThinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pSprite->owner == pPlayer->at5b || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ }
+ }
+}
+
+void sub_5F15C(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pSprite->owner == pPlayer->at5b || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ }
+ if (pXSprite->state)
+ {
+ char va4[(kMaxSectors+7)>>3];
+ gAffectedSectors[0] = 0;
+ gAffectedXWalls[0] = 0;
+ GetClosestSpriteSectors(pSprite->sectnum, pSprite->x, pSprite->y, 400, gAffectedSectors, va4, gAffectedXWalls);
+ for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ int dx = pSprite2->x-pSprite->x;
+ int dy = pSprite2->y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (pSprite2->type == 245)
+ {
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite2->type-kDudeBase];
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ int UNUSED(nAngle) = getangle(dx,dy);
+ aiSetTarget(pXSprite, pSprite2->index);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ }
+ }
+ }
+}
+
+void aiProcessDudes(void)
+{
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ if (pSprite->type >= kDudePlayer1 && pSprite->type <= kDudePlayer8)
+ continue;
+ if (pXSprite->health == 0)
+ continue;
+ pXSprite->stateTimer = ClipLow(pXSprite->stateTimer-4, 0);
+ if (pXSprite->aiState->at10)
+ pXSprite->aiState->at10(pSprite, pXSprite);
+ if (pXSprite->aiState->at14 && (gFrame&3) == (nSprite&3))
+ pXSprite->aiState->at14(pSprite, pXSprite);
+ if (pXSprite->stateTimer == 0 && pXSprite->aiState->at18)
+ {
+ if (pXSprite->aiState->at8 > 0)
+ aiNewState(pSprite, pXSprite, pXSprite->aiState->at18);
+ else if (seqGetStatus(3, nXSprite) < 0)
+ aiNewState(pSprite, pXSprite, pXSprite->aiState->at18);
+ }
+ if (pXSprite->health > 0 && ((pDudeInfo->hinderDamage<<4) <= cumulDamage[nXSprite]))
+ {
+ pXSprite->cumulDamage = cumulDamage[nXSprite];
+ RecoilDude(pSprite, pXSprite);
+ }
+ }
+ memset(cumulDamage, 0, sizeof(cumulDamage));
+}
+
+void aiInit(void)
+{
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ aiInitSprite(&sprite[nSprite]);
+ }
+}
+
+void aiInitSprite(spritetype *pSprite)
+{
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSector = pSprite->sectnum;
+ int nXSector = sector[nSector].extra;
+ XSECTOR *pXSector = NULL;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ DUDEEXTRA *pDudeExtra = &gDudeExtra[pSprite->extra];
+ pDudeExtra->at4 = 0;
+ pDudeExtra->at0 = 0;
+ switch (pSprite->type)
+ {
+ case kGDXDudeUniversalCultist:
+ {
+ DUDEEXTRA_at6_u1* pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ pDudeExtraE->at4 = 0;
+ aiNewState(pSprite, pXSprite, &GDXGenDudeIdleL);
+ break;
+ }
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnGoto);
+ pXSprite->burnTime = 1200;
+ break;
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ case 249:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &cultistIdle);
+ break;
+ }
+ case 230:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &fanaticProneIdle);
+ break;
+ }
+ case 246:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &cultistProneIdle);
+ break;
+ }
+ case 204:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u2;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &zombieFIdle);
+ break;
+ }
+ case 203:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u2;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &zombieAIdle);
+ break;
+ }
+ case 244:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u2;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &zombieSIdle);
+ pSprite->hitag &= ~1;
+ break;
+ }
+ case 205:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u2;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &zombieEIdle);
+ break;
+ }
+ case 206:
+ case 207:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &gargoyleFIdle);
+ break;
+ }
+ case 208:
+ case 209:
+ aiNewState(pSprite, pXSprite, &gargoyleStatueIdle);
+ break;
+ case 227:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u2;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &cerberusIdle);
+ break;
+ }
+ case 211:
+ aiNewState(pSprite, pXSprite, &houndIdle);
+ break;
+ case 212:
+ aiNewState(pSprite, pXSprite, &handIdle);
+ break;
+ case 210:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &ghostIdle);
+ break;
+ }
+ case 245:
+ aiNewState(pSprite, pXSprite, &innocentIdle);
+ break;
+ case 220:
+ aiNewState(pSprite, pXSprite, &ratIdle);
+ break;
+ case 218:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &eelIdle);
+ break;
+ }
+ case 217:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastIdle);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastIdle);
+ break;
+ case 219:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &batIdle);
+ break;
+ }
+ case 213:
+ case 214:
+ case 215:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &spidIdle);
+ break;
+ }
+ case 216:
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &spidIdle);
+ break;
+ }
+ case 229:
+ {
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[nXSprite].at6.u2;
+ pDudeExtraE->at4 = 0;
+ pDudeExtraE->at0 = 0;
+ aiNewState(pSprite, pXSprite, &tchernobogIdle);
+ break;
+ }
+ case 250:
+ aiNewState(pSprite, pXSprite, &tinycalebIdle);
+ break;
+ case 251:
+ aiNewState(pSprite, pXSprite, &beastIdle);
+ break;
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &podIdle);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacleIdle);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &genIdle);
+ break;
+ }
+ aiSetTarget(pXSprite, 0, 0, 0);
+ pXSprite->stateTimer = 0;
+ switch (pSprite->type)
+ {
+ case 213:
+ case 214:
+ case 215:
+ if (pSprite->cstat&8)
+ pSprite->hitag |= 9;
+ else
+ pSprite->hitag = 15;
+ break;
+ case 206:
+ case 207:
+ case 210:
+ case 218:
+ case 219:
+ pSprite->hitag |= 9;
+ break;
+ case 217:
+ if (pXSector && pXSector->Underwater)
+ pSprite->hitag |= 9;
+ else
+ pSprite->hitag = 15;
+ break;
+ case 205:
+ case 244:
+ pSprite->hitag = 7;
+ break;
+ // By NoOne: Allow put pods and tentacles on ceilings if sprite is y-flipped.
+ case 221:
+ case 222:
+ case 223:
+ case 224:
+ case 225:
+ case 226:
+ if ((pSprite->cstat & kSprFlipY) != 0) {
+ if (!(pSprite->hitag & kHitagExtBit)) // don't add autoaim for player if hitag 1 specified in editor.
+ pSprite->hitag = kHitagAutoAim;
+ break;
+ }
+ fallthrough__;
+ // go default
+ default:
+ pSprite->hitag = 15;
+ break;
+ }
+}
+
+class AILoadSave : public LoadSave
+{
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+void AILoadSave::Load(void)
+{
+ Read(cumulDamage, sizeof(cumulDamage));
+ Read(gDudeSlope, sizeof(gDudeSlope));
+}
+
+void AILoadSave::Save(void)
+{
+ Write(cumulDamage, sizeof(cumulDamage));
+ Write(gDudeSlope, sizeof(gDudeSlope));
+}
+
+static AILoadSave *myLoadSave;
+
+void AILoadSaveConstruct(void)
+{
+ myLoadSave = new AILoadSave();
+}
+
diff --git a/source/blood/src/ai.h b/source/blood/src/ai.h
new file mode 100644
index 000000000..b69e83644
--- /dev/null
+++ b/source/blood/src/ai.h
@@ -0,0 +1,105 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "compat.h"
+#include "common_game.h"
+#include "actor.h"
+#include "db.h"
+
+struct AISTATE {
+ int stateType; // By NoOne: current type of state. Basically required for kGDXDudeTargetChanger, but can be used for something else.
+ int at0; // seq
+ int at4; // seq callback
+ int at8;
+ void(*atc)(spritetype *, XSPRITE *);
+ void(*at10)(spritetype *, XSPRITE *);
+ void(*at14)(spritetype *, XSPRITE *);
+ AISTATE *at18; // next state ?
+};
+extern AISTATE aiState[];
+
+enum AI_SFX_PRIORITY {
+ AI_SFX_PRIORITY_0 = 0,
+ AI_SFX_PRIORITY_1,
+ AI_SFX_PRIORITY_2,
+};
+
+
+struct DUDEEXTRA_at6_u1
+{
+ int at0;
+ int at4;
+ char at8;
+};
+
+struct DUDEEXTRA_at6_u2
+{
+ int at0;
+ char at4;
+};
+
+struct DUDEEXTRA
+{
+ int at0;
+ char at4;
+ AI_SFX_PRIORITY at5;
+ union
+ {
+ DUDEEXTRA_at6_u1 u1;
+ DUDEEXTRA_at6_u2 u2;
+ } at6;
+ //DUDEEXTRA_at6 at6;
+};
+
+struct TARGETTRACK {
+ int at0;
+ int at4;
+ int at8; // view angle
+ int atc;
+ int at10; // Move predict
+};
+
+extern int dword_138BB0[5];
+extern DUDEEXTRA gDudeExtra[];
+extern int gDudeSlope[];
+
+bool sub_5BDA8(spritetype *pSprite, int nSeq);
+void aiPlay3DSound(spritetype *pSprite, int a2, AI_SFX_PRIORITY a3, int a4);
+void aiNewState(spritetype *pSprite, XSPRITE *pXSprite, AISTATE *pAIState);
+void aiChooseDirection(spritetype *pSprite, XSPRITE *pXSprite, int a3);
+void aiMoveForward(spritetype *pSprite, XSPRITE *pXSprite);
+void aiMoveTurn(spritetype *pSprite, XSPRITE *pXSprite);
+void aiMoveDodge(spritetype *pSprite, XSPRITE *pXSprite);
+void aiActivateDude(spritetype *pSprite, XSPRITE *pXSprite);
+void aiSetTarget(XSPRITE *pXSprite, int x, int y, int z);
+void aiSetTarget(XSPRITE *pXSprite, int nTarget);
+int aiDamageSprite(spritetype *pSprite, XSPRITE *pXSprite, int nSource, DAMAGE_TYPE nDmgType, int nDamage);
+void aiThinkTarget(spritetype *pSprite, XSPRITE *pXSprite);
+void sub_5F15C(spritetype *pSprite, XSPRITE *pXSprite);
+void aiProcessDudes(void);
+void aiInit(void);
+void aiInitSprite(spritetype *pSprite);
+
+// By NoOne: this function required for kGDXDudeTargetChanger
+void aiSetGenIdleState(spritetype* pSprite, XSPRITE* pXSprite);
diff --git a/source/blood/src/aibat.cpp b/source/blood/src/aibat.cpp
new file mode 100644
index 000000000..f138c9cbd
--- /dev/null
+++ b/source/blood/src/aibat.cpp
@@ -0,0 +1,437 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aibat.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "player.h"
+#include "seq.h"
+#include "trig.h"
+
+static void BiteSeqCallback(int, int);
+static void thinkTarget(spritetype *, XSPRITE *);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkPonder(spritetype *, XSPRITE *);
+static void MoveDodgeUp(spritetype *, XSPRITE *);
+static void MoveDodgeDown(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void MoveForward(spritetype *, XSPRITE *);
+static void MoveSwoop(spritetype *, XSPRITE *);
+static void MoveFly(spritetype *, XSPRITE *);
+static void MoveToCeil(spritetype *, XSPRITE *);
+
+static int nBiteClient = seqRegisterClient(BiteSeqCallback);
+
+AISTATE batIdle = {kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE batFlyIdle = {kAiStateIdle, 6, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE batChase = {kAiStateChase, 6, -1, 0, NULL, MoveForward, thinkChase, &batFlyIdle };
+AISTATE batPonder = {kAiStateOther, 6, -1, 0, NULL, NULL, thinkPonder, NULL };
+AISTATE batGoto = {kAiStateMove, 6, -1, 600, NULL, MoveForward, thinkGoto, &batFlyIdle };
+AISTATE batBite = {kAiStateChase, 7, nBiteClient, 60, NULL, NULL, NULL, &batPonder };
+AISTATE batRecoil = {kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &batChase };
+AISTATE batSearch = {kAiStateSearch, 6, -1, 120, NULL, MoveForward, thinkSearch, &batFlyIdle };
+AISTATE batSwoop = {kAiStateOther, 6, -1, 60, NULL, MoveSwoop, thinkChase, &batChase };
+AISTATE batFly = { kAiStateMove, 6, -1, 0, NULL, MoveFly, thinkChase, &batChase };
+AISTATE batTurn = {kAiStateMove, 6, -1, 60, NULL, aiMoveTurn, NULL, &batChase };
+AISTATE batHide = {kAiStateOther, 6, -1, 0, NULL, MoveToCeil, MoveForward, NULL };
+AISTATE batDodgeUp = {kAiStateMove, 6, -1, 120, NULL, MoveDodgeUp, 0, &batChase };
+AISTATE batDodgeUpRight = {kAiStateMove, 6, -1, 90, NULL, MoveDodgeUp, 0, &batChase };
+AISTATE batDodgeUpLeft = {kAiStateMove, 6, -1, 90, NULL, MoveDodgeUp, 0, &batChase };
+AISTATE batDodgeDown = {kAiStateMove, 6, -1, 120, NULL, MoveDodgeDown, 0, &batChase };
+AISTATE batDodgeDownRight = {kAiStateMove, 6, -1, 90, NULL, MoveDodgeDown, 0, &batChase };
+AISTATE batDodgeDownLeft = {kAiStateMove, 6, -1, 90, NULL, MoveDodgeDown, 0, &batChase };
+
+static void BiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ spritetype *pSprite = &sprite[pXSprite->reference];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type-kDudeBase];
+ int height = (pSprite->yrepeat*pDudeInfo->eyeHeight)<<2;
+ int height2 = (pTarget->yrepeat*pDudeInfoT->eyeHeight)<<2;
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ actFireVector(pSprite, 0, 0, dx, dy, height2-height, VECTOR_TYPE_6);
+}
+
+static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
+ pDudeExtraE->at4++;
+ else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
+ {
+ pDudeExtraE->at4 = 0;
+ pXSprite->goalAng += 256;
+ POINT3D *pTarget = &baseSprite[pSprite->index];
+ aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
+ aiNewState(pSprite, pXSprite, &batTurn);
+ return;
+ }
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else
+ continue;
+ break;
+ }
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ thinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &batSearch);
+ thinkTarget(pSprite, pXSprite);
+}
+
+static void thinkPonder(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &batSearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &batSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ int height2 = (dudeInfo[pTarget->type-kDudeBase].eyeHeight*pTarget->yrepeat)<<2;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (height2-height < 0x3000 && nDist < 0x1800 && nDist > 0xc00 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batDodgeUp);
+ else if (height2-height > 0x5000 && nDist < 0x1800 && nDist > 0xc00 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batDodgeDown);
+ else if (height2-height < 0x2000 && nDist < 0x200 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batDodgeUp);
+ else if (height2-height > 0x6000 && nDist < 0x1400 && nDist > 0x800 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batDodgeDown);
+ else if (height2-height < 0x2000 && nDist < 0x1400 && nDist > 0x800 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batDodgeUp);
+ else if (height2-height < 0x2000 && klabs(nDeltaAngle) < 85 && nDist > 0x1400)
+ aiNewState(pSprite, pXSprite, &batDodgeUp);
+ else if (height2-height > 0x4000)
+ aiNewState(pSprite, pXSprite, &batDodgeDown);
+ else
+ aiNewState(pSprite, pXSprite, &batDodgeUp);
+ return;
+ }
+ }
+ aiNewState(pSprite, pXSprite, &batGoto);
+ pXSprite->target = -1;
+}
+
+static void MoveDodgeUp(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -0x52aaa;
+}
+
+static void MoveDodgeDown(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (pXSprite->dodgeDir == 0)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = 0x44444;
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &batGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &batSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &batSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ // Should be dudeInfo[pTarget->type-kDudeBase]
+ int height2 = (pDudeInfo->eyeHeight*pTarget->yrepeat)<<2;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int floorZ = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ if (height2-height < 0x2000 && nDist < 0x200 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batBite);
+ else if ((height2-height > 0x5000 || floorZ-bottom > 0x5000) && nDist < 0x1400 && nDist > 0x800 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batSwoop);
+ else if ((height2-height < 0x3000 || floorZ-bottom < 0x3000) && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &batFly);
+ return;
+ }
+ }
+ else
+ {
+ aiNewState(pSprite, pXSprite, &batFly);
+ return;
+ }
+ }
+
+ pXSprite->target = -1;
+ aiNewState(pSprite, pXSprite, &batHide);
+}
+
+static void MoveForward(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if ((unsigned int)Random(64) < 32 && nDist <= 0x200)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void MoveSwoop(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x200)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = 0x44444;
+}
+
+static void MoveFly(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pSprite->ang = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x200)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -0x2d555;
+}
+
+void MoveToCeil(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int nSector = pSprite->sectnum;
+ if (z - pXSprite->targetZ < 0x1000)
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pSprite->hitag = 0;
+ aiNewState(pSprite, pXSprite, &batIdle);
+ }
+ else
+ aiSetTarget(pXSprite, x, y, sector[nSector].ceilingz);
+}
diff --git a/source/blood/src/aibat.h b/source/blood/src/aibat.h
new file mode 100644
index 000000000..464644610
--- /dev/null
+++ b/source/blood/src/aibat.h
@@ -0,0 +1,43 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE batIdle;
+extern AISTATE batFlyIdle;
+extern AISTATE batChase;
+extern AISTATE batPonder;
+extern AISTATE batGoto;
+extern AISTATE batBite;
+extern AISTATE batRecoil;
+extern AISTATE batSearch;
+extern AISTATE batSwoop;
+extern AISTATE batFly;
+extern AISTATE batTurn;
+extern AISTATE batHide;
+extern AISTATE batDodgeUp;
+extern AISTATE batDodgeUpRight;
+extern AISTATE batDodgeUpLeft;
+extern AISTATE batDodgeDown;
+extern AISTATE batDodgeDownRight;
+extern AISTATE batDodgeDownLeft;
diff --git a/source/blood/src/aibeast.cpp b/source/blood/src/aibeast.cpp
new file mode 100644
index 000000000..01f6bc010
--- /dev/null
+++ b/source/blood/src/aibeast.cpp
@@ -0,0 +1,581 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aibeast.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void SlashSeqCallback(int, int);
+static void StompSeqCallback(int, int);
+static void MorphToBeast(spritetype *, XSPRITE *);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void thinkSwimGoto(spritetype *, XSPRITE *);
+static void thinkSwimChase(spritetype *, XSPRITE *);
+static void MoveForward(spritetype *, XSPRITE *);
+static void sub_628A0(spritetype *, XSPRITE *);
+static void sub_62AE0(spritetype *, XSPRITE *);
+static void sub_62D7C(spritetype *, XSPRITE *);
+
+static int nSlashClient = seqRegisterClient(SlashSeqCallback);
+static int nStompClient = seqRegisterClient(StompSeqCallback);
+
+AISTATE beastIdle = {kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE beastChase = {kAiStateChase, 8, -1, 0, NULL, MoveForward, thinkChase, NULL };
+AISTATE beastDodge = { kAiStateMove, 8, -1, 60, NULL, aiMoveDodge, NULL, &beastChase };
+AISTATE beastGoto = { kAiStateMove, 8, -1, 600, NULL, MoveForward, thinkGoto, &beastIdle };
+AISTATE beastSlash = { kAiStateChase, 6, nSlashClient, 120, NULL, NULL, NULL, &beastChase };
+AISTATE beastStomp = { kAiStateChase, 7, nStompClient, 120, NULL, NULL, NULL, &beastChase };
+AISTATE beastSearch = { kAiStateSearch, 8, -1, 120, NULL, MoveForward, thinkSearch, &beastIdle };
+AISTATE beastRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &beastDodge };
+AISTATE beastTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &beastDodge };
+AISTATE beastSwimIdle = {kAiStateIdle, 9, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE beastSwimChase = { kAiStateChase, 9, -1, 0, NULL, sub_628A0, thinkSwimChase, NULL };
+AISTATE beastSwimDodge = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &beastSwimChase };
+AISTATE beastSwimGoto = { kAiStateMove, 9, -1, 600, NULL, MoveForward, thinkSwimGoto, &beastSwimIdle };
+AISTATE beastSwimSearch = { kAiStateSearch, 9, -1, 120, NULL, MoveForward, thinkSearch, &beastSwimIdle };
+AISTATE beastSwimSlash = { kAiStateChase, 9, nSlashClient, 0, NULL, NULL, thinkSwimChase, &beastSwimChase };
+AISTATE beastSwimRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &beastSwimDodge };
+AISTATE beastMorphToBeast = { kAiStateOther, -1, -1, 0, MorphToBeast, NULL, NULL, &beastIdle };
+AISTATE beastMorphFromCultist = { kAiStateOther, 2576, -1, 0, NULL, NULL, NULL, &beastMorphToBeast };
+AISTATE beast138FB4 = { kAiStateOther, 9, -1, 120, NULL, sub_62AE0, thinkSwimChase, &beastSwimChase };
+AISTATE beast138FD0 = { kAiStateOther, 9, -1, 0, NULL, sub_62D7C, thinkSwimChase, &beastSwimChase };
+AISTATE beast138FEC = { kAiStateOther, 9, -1, 120, NULL, aiMoveTurn, NULL, &beastSwimChase };
+
+static void SlashSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ // Correct ?
+ int dz = pSprite->z-pTarget->z;
+ dx += Random3(4000-700*gGameOptions.nDifficulty);
+ dy += Random3(4000-700*gGameOptions.nDifficulty);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_13);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_13);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_13);
+ sfxPlay3DSound(pSprite, 9012+Random(2), -1, 0);
+}
+
+static void StompSeqCallback(int, int nXSprite)
+{
+ char vb8[(kMaxSectors+7)>>3];
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int vc = 400;
+ int nSector = pSprite->sectnum;
+ int v1c = 5+2*gGameOptions.nDifficulty;
+ int v10 = 25+30*gGameOptions.nDifficulty;
+ gAffectedSectors[0] = -1;
+ gAffectedXWalls[0] = -1;
+ GetClosestSpriteSectors(nSector, x, y, vc, gAffectedSectors, vb8, gAffectedXWalls);
+ char v4 = 0;
+ int v34 = -1;
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ actHitcodeToData(hit, &gHitInfo, &v34, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (hit == 3 && v34 >= 0)
+ {
+ if (sprite[v34].statnum == 6)
+ v4 = 0;
+ }
+ vc <<= 4;
+ for (int nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ if (nSprite != nSprite2 || v4)
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite2->extra > 0 && pSprite2->extra < kMaxXSprites)
+ {
+ if (pSprite2->type == 251)
+ continue;
+ if (pSprite2->hitag&32)
+ continue;
+ if (TestBitString(vb8, pSprite2->sectnum) && CheckProximity(pSprite2, x, y, z, nSector, vc))
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (klabs(bottom-sector[nSector].floorz) == 0)
+ {
+ int dx = klabs(pSprite->x-pSprite2->x);
+ int dy = klabs(pSprite->y-pSprite2->y);
+ int nDist2 = ksqrt(dx*dx + dy*dy);
+ if (nDist2 <= vc)
+ {
+ int nDamage;
+ if (!nDist2)
+ nDamage = v1c + v10;
+ else
+ nDamage = v1c + ((vc-nDist2)*v10)/vc;
+ if (IsPlayerSprite(pSprite2))
+ gPlayer[pSprite2->type-kDudePlayer1].at37f += nDamage*4;
+ actDamageSprite(nSprite, pSprite2, DAMAGE_TYPE_0, nDamage<<4);
+ }
+ }
+ }
+ }
+ }
+ }
+ for (int nSprite2 = headspritestat[4]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite2->hitag&32)
+ continue;
+ if (TestBitString(vb8, pSprite2->sectnum) && CheckProximity(pSprite2, x, y, z, nSector, vc))
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite2->extra];
+ if (pXSprite->locked)
+ continue;
+ int dx = klabs(pSprite->x-pSprite2->x);
+ int dy = klabs(pSprite->y-pSprite2->y);
+ int nDist2 = ksqrt(dx*dx + dy*dy);
+ if (nDist2 <= vc)
+ {
+ int nDamage;
+ if (!nDist2)
+ nDamage = v1c + v10;
+ else
+ nDamage = v1c + ((vc-nDist2)*v10)/vc;
+ if (IsPlayerSprite(pSprite2))
+ gPlayer[pSprite2->type-kDudePlayer1].at37f += nDamage*4;
+ actDamageSprite(nSprite, pSprite2, DAMAGE_TYPE_0, nDamage<<4);
+ }
+ }
+ }
+ sfxPlay3DSound(pSprite, 9015+Random(2), -1, 0);
+}
+
+static void MorphToBeast(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ actHealDude(pXSprite, dudeInfo[51].startHealth, dudeInfo[51].startHealth);
+ pSprite->type = 251;
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &beastSearch);
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &beastSearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &beastSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &beastSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int nXSprite = sprite[pXSprite->reference].extra;
+ gDudeSlope[nXSprite] = divscale(pTarget->z-pSprite->z, nDist, 10);
+ if (nDist < 0x1400 && nDist > 0xa00 && klabs(nDeltaAngle) < 85 && (pTarget->hitag&2)
+ && IsPlayerSprite(pTarget) && Chance(0x8000))
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ if (pXTarget->health > gPlayerTemplate[0].startHealth/2)
+ {
+ switch (hit)
+ {
+ case -1:
+ if (!pXSector || !pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastStomp);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ {
+ if (!pXSector || !pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastStomp);
+ }
+ else
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimDodge);
+ else
+ aiNewState(pSprite, pXSprite, &beastDodge);
+ }
+ break;
+ default:
+ if (!pXSector || !pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastStomp);
+ break;
+ }
+ }
+ }
+ if (nDist < 921 && klabs(nDeltaAngle) < 28)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSlash);
+ else
+ aiNewState(pSprite, pXSprite, &beastSlash);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSlash);
+ else
+ aiNewState(pSprite, pXSprite, &beastSlash);
+ }
+ else
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimDodge);
+ else
+ aiNewState(pSprite, pXSprite, &beastDodge);
+ }
+ break;
+ default:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimSlash);
+ else
+ aiNewState(pSprite, pXSprite, &beastSlash);
+ break;
+ }
+ }
+ }
+ return;
+ }
+ }
+
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &beastSwimGoto);
+ else
+ aiNewState(pSprite, pXSprite, &beastGoto);
+ pXSprite->target = -1;
+}
+
+static void thinkSwimGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkSwimChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &beastSwimGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &beastSwimSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = pDudeInfo->eyeHeight+pSprite->z;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int UNUSED(floorZ) = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &beastSwimSlash);
+ else
+ {
+ aiPlay3DSound(pSprite, 9009+Random(2), AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &beast138FD0);
+ }
+ }
+ }
+ else
+ aiNewState(pSprite, pXSprite, &beast138FD0);
+ return;
+ }
+ aiNewState(pSprite, pXSprite, &beastSwimGoto);
+ pXSprite->target = -1;
+}
+
+static void MoveForward(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (klabs(nAng) > 341)
+ return;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (nDist <= 0x400 && Random(64) < 32)
+ return;
+ xvel[nSprite] += mulscale30(pDudeInfo->frontSpeed, Cos(pSprite->ang));
+ yvel[nSprite] += mulscale30(pDudeInfo->frontSpeed, Sin(pSprite->ang));
+}
+
+static void sub_628A0(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Random(64) < 32 && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>2;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void sub_62AE0(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int z = pSprite->z + dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+ int z2 = pTarget->z + dudeInfo[pTarget->type - kDudeBase].eyeHeight;
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int dz = z2 - z;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -dz;
+}
+
+static void sub_62D7C(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int z = pSprite->z + dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+ int z2 = pTarget->z + dudeInfo[pTarget->type - kDudeBase].eyeHeight;
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pSprite->ang = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int dz = (z2 - z)<<3;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = dz;
+}
diff --git a/source/blood/src/aibeast.h b/source/blood/src/aibeast.h
new file mode 100644
index 000000000..66451dbbb
--- /dev/null
+++ b/source/blood/src/aibeast.h
@@ -0,0 +1,46 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE beastIdle;
+extern AISTATE beastChase;
+extern AISTATE beastDodge;
+extern AISTATE beastGoto;
+extern AISTATE beastSlash;
+extern AISTATE beastStomp;
+extern AISTATE beastSearch;
+extern AISTATE beastRecoil;
+extern AISTATE beastTeslaRecoil;
+extern AISTATE beastSwimIdle;
+extern AISTATE beastSwimChase;
+extern AISTATE beastSwimDodge;
+extern AISTATE beastSwimGoto;
+extern AISTATE beastSwimSearch;
+extern AISTATE beastSwimSlash;
+extern AISTATE beastSwimRecoil;
+extern AISTATE beastMorphToBeast;
+extern AISTATE beastMorphFromCultist;
+extern AISTATE beast138FB4;
+extern AISTATE beast138FD0;
+extern AISTATE beast138FEC;
diff --git a/source/blood/src/aiboneel.cpp b/source/blood/src/aiboneel.cpp
new file mode 100644
index 000000000..35e8921ef
--- /dev/null
+++ b/source/blood/src/aiboneel.cpp
@@ -0,0 +1,442 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aiboneel.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void BiteSeqCallback(int, int);
+static void thinkTarget(spritetype *, XSPRITE *);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkPonder(spritetype *, XSPRITE *);
+static void MoveDodgeUp(spritetype *, XSPRITE *);
+static void MoveDodgeDown(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void MoveForward(spritetype *, XSPRITE *);
+static void MoveSwoop(spritetype *, XSPRITE *);
+static void MoveAscend(spritetype *pSprite, XSPRITE *pXSprite);
+static void MoveToCeil(spritetype *, XSPRITE *);
+
+static int nBiteClient = seqRegisterClient(BiteSeqCallback);
+
+AISTATE eelIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE eelFlyIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE eelChase = { kAiStateChase, 0, -1, 0, NULL, MoveForward, thinkChase, &eelIdle };
+AISTATE eelPonder = { kAiStateOther, 0, -1, 0, NULL, NULL, thinkPonder, NULL };
+AISTATE eelGoto = { kAiStateMove, 0, -1, 600, NULL, NULL, thinkGoto, &eelIdle };
+AISTATE eelBite = { kAiStateChase, 7, nBiteClient, 60, NULL, NULL, NULL, &eelChase };
+AISTATE eelRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &eelChase };
+AISTATE eelSearch = { kAiStateSearch, 0, -1, 120, NULL, MoveForward, thinkSearch, &eelIdle };
+AISTATE eelSwoop = { kAiStateOther, 0, -1, 60, NULL, MoveSwoop, thinkChase, &eelChase };
+AISTATE eelFly = { kAiStateMove, 0, -1, 0, NULL, MoveAscend, thinkChase, &eelChase };
+AISTATE eelTurn = { kAiStateMove, 0, -1, 60, NULL, aiMoveTurn, NULL, &eelChase };
+AISTATE eelHide = { kAiStateOther, 0, -1, 0, NULL, MoveToCeil, MoveForward, NULL };
+AISTATE eelDodgeUp = { kAiStateMove, 0, -1, 120, NULL, MoveDodgeUp, NULL, &eelChase };
+AISTATE eelDodgeUpRight = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeUp, NULL, &eelChase };
+AISTATE eelDodgeUpLeft = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeUp, NULL, &eelChase };
+AISTATE eelDodgeDown = { kAiStateMove, 0, -1, 120, NULL, MoveDodgeDown, NULL, &eelChase };
+AISTATE eelDodgeDownRight = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeDown, NULL, &eelChase };
+AISTATE eelDodgeDownLeft = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeDown, NULL, &eelChase };
+
+static void BiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ spritetype *pSprite = &sprite[pXSprite->reference];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type-kDudeBase];
+ int height = (pSprite->yrepeat*pDudeInfo->eyeHeight)<<2;
+ int height2 = (pTarget->yrepeat*pDudeInfoT->eyeHeight)<<2;
+ /*
+ * workaround for
+ * pXSprite->target >= 0 && pXSprite->target < kMaxSprites in file NBlood/source/blood/src/aiboneel.cpp at line 86
+ * The value of pXSprite->target is -1.
+ * copied from lines 177:181
+ * resolves this case, but may cause other issues?
+ */
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ return;
+ }
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ actFireVector(pSprite, 0, 0, dx, dy, height2-height, VECTOR_TYPE_7);
+}
+
+static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
+ pDudeExtraE->at4++;
+ else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
+ {
+ pDudeExtraE->at4 = 0;
+ pXSprite->goalAng += 256;
+ POINT3D *pTarget = &baseSprite[pSprite->index];
+ aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
+ aiNewState(pSprite, pXSprite, &eelTurn);
+ return;
+ }
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ pDudeExtraE->at4 = 0;
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ pDudeExtraE->at4 = 0;
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else
+ continue;
+ break;
+ }
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ thinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ thinkTarget(pSprite, pXSprite);
+}
+
+static void thinkPonder(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ int height2 = (dudeInfo[pTarget->type-kDudeBase].eyeHeight*pTarget->yrepeat)<<2;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (height2-height < -0x2000 && nDist < 0x1800 && nDist > 0xc00 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelDodgeUp);
+ else if (height2-height > 0xccc && nDist < 0x1800 && nDist > 0xc00 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelDodgeDown);
+ else if (height2-height < 0xccc && nDist < 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelDodgeUp);
+ else if (height2-height > 0xccc && nDist < 0x1400 && nDist > 0x800 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelDodgeDown);
+ else if (height2-height < -0x2000 && nDist < 0x1400 && nDist > 0x800 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelDodgeUp);
+ else if (height2-height < -0x2000 && klabs(nDeltaAngle) < 85 && nDist > 0x1400)
+ aiNewState(pSprite, pXSprite, &eelDodgeUp);
+ else if (height2-height > 0xccc)
+ aiNewState(pSprite, pXSprite, &eelDodgeDown);
+ else
+ aiNewState(pSprite, pXSprite, &eelDodgeUp);
+ return;
+ }
+ }
+ aiNewState(pSprite, pXSprite, &eelGoto);
+ pXSprite->target = -1;
+}
+
+static void MoveDodgeUp(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -0x8000;
+}
+
+static void MoveDodgeDown(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (pXSprite->dodgeDir == 0)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = 0x44444;
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &eelGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &eelSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int top2, bottom2;
+ GetSpriteExtents(pTarget, &top2, &bottom2);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x399 && top2 > top && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelSwoop);
+ else if (nDist <= 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelBite);
+ else if (bottom2 > top && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelSwoop);
+ else if (top2 < top && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &eelFly);
+ }
+ }
+ return;
+ }
+
+ pXSprite->target = -1;
+ aiNewState(pSprite, pXSprite, &eelSearch);
+}
+
+static void MoveForward(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = (pDudeInfo->frontSpeed-(((4-gGameOptions.nDifficulty)<<26)/120)/120)<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (nDist <= 0x399)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void MoveSwoop(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = (pDudeInfo->frontSpeed-(((4-gGameOptions.nDifficulty)<<26)/120)/120)<<2;
+ if (klabs(nAng) > 341)
+ return;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x8000) && nDist <= 0x399)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = 0x22222;
+}
+
+static void MoveAscend(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = (pDudeInfo->frontSpeed-(((4-gGameOptions.nDifficulty)<<26)/120)/120)<<2;
+ if (klabs(nAng) > 341)
+ return;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x399)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -0x8000;
+}
+
+void MoveToCeil(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int nSector = pSprite->sectnum;
+ if (z - pXSprite->targetZ < 0x1000)
+ {
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ pDudeExtraE->at8 = 0;
+ pSprite->hitag = 0;
+ aiNewState(pSprite, pXSprite, &eelIdle);
+ }
+ else
+ aiSetTarget(pXSprite, x, y, sector[nSector].ceilingz);
+}
diff --git a/source/blood/src/aiboneel.h b/source/blood/src/aiboneel.h
new file mode 100644
index 000000000..b73cc1a7c
--- /dev/null
+++ b/source/blood/src/aiboneel.h
@@ -0,0 +1,44 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE eelIdle;
+extern AISTATE eelFlyIdle;
+extern AISTATE eelChase;
+extern AISTATE eelPonder;
+extern AISTATE eelGoto;
+extern AISTATE eelBite;
+extern AISTATE eelRecoil;
+extern AISTATE eelSearch;
+extern AISTATE eelSwoop;
+extern AISTATE eelFly;
+extern AISTATE eelTurn;
+extern AISTATE eelHide;
+extern AISTATE eelDodgeUp;
+extern AISTATE eelDodgeUpRight;
+extern AISTATE eelDodgeUpLeft;
+extern AISTATE eelDodgeDown;
+extern AISTATE eelDodgeDownRight;
+extern AISTATE eelDodgeDownLeft;
+
diff --git a/source/blood/src/aiburn.cpp b/source/blood/src/aiburn.cpp
new file mode 100644
index 000000000..307561bee
--- /dev/null
+++ b/source/blood/src/aiburn.cpp
@@ -0,0 +1,267 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aiburn.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void BurnSeqCallback(int, int);
+static void thinkSearch(spritetype*, XSPRITE*);
+static void thinkGoto(spritetype*, XSPRITE*);
+static void thinkChase(spritetype*, XSPRITE*);
+
+static int nBurnClient = seqRegisterClient(BurnSeqCallback);
+
+AISTATE cultistBurnIdle = { kAiStateIdle, 3, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE cultistBurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cultistBurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &cultistBurnSearch };
+AISTATE cultistBurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, &cultistBurnSearch };
+AISTATE cultistBurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &cultistBurnChase };
+
+AISTATE zombieABurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieABurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &zombieABurnSearch };
+AISTATE zombieABurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, NULL };
+AISTATE zombieABurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &zombieABurnChase };
+
+AISTATE zombieFBurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieFBurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &zombieFBurnSearch };
+AISTATE zombieFBurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, NULL };
+AISTATE zombieFBurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &zombieFBurnChase };
+
+AISTATE innocentBurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE innocentBurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &zombieFBurnSearch };
+AISTATE innocentBurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, NULL };
+AISTATE innocentBurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &zombieFBurnChase };
+
+AISTATE beastBurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE beastBurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &beastBurnSearch };
+AISTATE beastBurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, &beastBurnSearch };
+AISTATE beastBurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &beastBurnChase };
+
+AISTATE tinycalebBurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE tinycalebBurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &tinycalebBurnSearch };
+AISTATE tinycalebBurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, &tinycalebBurnSearch };
+AISTATE tinycalebBurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &tinycalebBurnChase };
+
+AISTATE GDXGenDudeBurnIdle = { kAiStateIdle, 3, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE GDXGenDudeBurnChase = { kAiStateChase, 3, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE GDXGenDudeBurnGoto = { kAiStateMove, 3, -1, 3600, NULL, aiMoveForward, thinkGoto, &GDXGenDudeBurnSearch };
+AISTATE GDXGenDudeBurnSearch = { kAiStateSearch, 3, -1, 3600, NULL, aiMoveForward, thinkSearch, &GDXGenDudeBurnSearch };
+AISTATE GDXGenDudeBurnAttack = { kAiStateChase, 3, nBurnClient, 120, NULL, NULL, NULL, &GDXGenDudeBurnChase };
+
+static void BurnSeqCallback(int, int)
+{
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ switch (pSprite->type)
+ {
+ case 240:
+ aiNewState(pSprite, pXSprite, &cultistBurnSearch);
+ break;
+ case 241:
+ aiNewState(pSprite, pXSprite, &zombieABurnSearch);
+ break;
+ case 242:
+ aiNewState(pSprite, pXSprite, &zombieFBurnSearch);
+ break;
+ case 239:
+ aiNewState(pSprite, pXSprite, &innocentBurnSearch);
+ break;
+ case 253:
+ aiNewState(pSprite, pXSprite, &beastBurnSearch);
+ break;
+ case 252:
+ aiNewState(pSprite, pXSprite, &tinycalebBurnSearch);
+ break;
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnSearch);
+ break;
+ }
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ switch (pSprite->type)
+ {
+ case 240:
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ break;
+ case 241:
+ aiNewState(pSprite, pXSprite, &zombieABurnGoto);
+ break;
+ case 242:
+ aiNewState(pSprite, pXSprite, &zombieFBurnGoto);
+ break;
+ case 239:
+ aiNewState(pSprite, pXSprite, &innocentBurnGoto);
+ break;
+ case 253:
+ aiNewState(pSprite, pXSprite, &beastBurnGoto);
+ break;
+ case 252:
+ aiNewState(pSprite, pXSprite, &tinycalebBurnGoto);
+ break;
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnGoto);
+ break;
+ }
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ switch (pSprite->type)
+ {
+ case 240:
+ aiNewState(pSprite, pXSprite, &cultistBurnSearch);
+ break;
+ case 241:
+ aiNewState(pSprite, pXSprite, &zombieABurnSearch);
+ break;
+ case 242:
+ aiNewState(pSprite, pXSprite, &zombieFBurnSearch);
+ break;
+ case 239:
+ aiNewState(pSprite, pXSprite, &innocentBurnSearch);
+ break;
+ case 253:
+ aiNewState(pSprite, pXSprite, &beastBurnSearch);
+ break;
+ case 252:
+ aiNewState(pSprite, pXSprite, &tinycalebBurnSearch);
+ break;
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnSearch);
+ break;
+ }
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x333 && klabs(nDeltaAngle) < 85)
+ {
+ switch (pSprite->type)
+ {
+ case 240:
+ aiNewState(pSprite, pXSprite, &cultistBurnAttack);
+ break;
+ case 241:
+ aiNewState(pSprite, pXSprite, &zombieABurnAttack);
+ break;
+ case 242:
+ aiNewState(pSprite, pXSprite, &zombieFBurnAttack);
+ break;
+ case 239:
+ aiNewState(pSprite, pXSprite, &innocentBurnAttack);
+ break;
+ case 253:
+ aiNewState(pSprite, pXSprite, &beastBurnAttack);
+ break;
+ case 252:
+ aiNewState(pSprite, pXSprite, &tinycalebBurnAttack);
+ break;
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnSearch);
+ break;
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ switch (pSprite->type)
+ {
+ case 240:
+ aiNewState(pSprite, pXSprite, &cultistBurnGoto);
+ break;
+ case 241:
+ aiNewState(pSprite, pXSprite, &zombieABurnGoto);
+ break;
+ case 242:
+ aiNewState(pSprite, pXSprite, &zombieFBurnGoto);
+ break;
+ case 239:
+ aiNewState(pSprite, pXSprite, &innocentBurnGoto);
+ break;
+ case 253:
+ aiNewState(pSprite, pXSprite, &beastBurnGoto);
+ break;
+ case 252:
+ aiNewState(pSprite, pXSprite, &tinycalebBurnGoto);
+ break;
+ case kGDXGenDudeBurning:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeBurnSearch);
+ break;
+ }
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aiburn.h b/source/blood/src/aiburn.h
new file mode 100644
index 000000000..ff4cde645
--- /dev/null
+++ b/source/blood/src/aiburn.h
@@ -0,0 +1,56 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE cultistBurnIdle;
+extern AISTATE cultistBurnChase;
+extern AISTATE cultistBurnGoto;
+extern AISTATE cultistBurnSearch;
+extern AISTATE cultistBurnAttack;
+extern AISTATE zombieABurnChase;
+extern AISTATE zombieABurnGoto;
+extern AISTATE zombieABurnSearch;
+extern AISTATE zombieABurnAttack;
+extern AISTATE zombieFBurnChase;
+extern AISTATE zombieFBurnGoto;
+extern AISTATE zombieFBurnSearch;
+extern AISTATE zombieFBurnAttack;
+extern AISTATE innocentBurnChase;
+extern AISTATE innocentBurnGoto;
+extern AISTATE innocentBurnSearch;
+extern AISTATE innocentBurnAttack;
+extern AISTATE beastBurnChase;
+extern AISTATE beastBurnGoto;
+extern AISTATE beastBurnSearch;
+extern AISTATE beastBurnAttack;
+extern AISTATE tinycalebBurnChase;
+extern AISTATE tinycalebBurnGoto;
+extern AISTATE tinycalebBurnSearch;
+extern AISTATE tinycalebBurnAttack;
+extern AISTATE GDXGenDudeBurnIdle;
+extern AISTATE GDXGenDudeBurnChase;
+extern AISTATE GDXGenDudeBurnGoto;
+extern AISTATE GDXGenDudeBurnSearch;
+extern AISTATE GDXGenDudeBurnAttack;
+
diff --git a/source/blood/src/aicaleb.cpp b/source/blood/src/aicaleb.cpp
new file mode 100644
index 000000000..5ad414ded
--- /dev/null
+++ b/source/blood/src/aicaleb.cpp
@@ -0,0 +1,423 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aicaleb.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void SeqAttackCallback(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void thinkSwimGoto(spritetype *, XSPRITE *);
+static void thinkSwimChase(spritetype *, XSPRITE *);
+static void sub_65D04(spritetype *, XSPRITE *);
+static void sub_65F44(spritetype *, XSPRITE *);
+static void sub_661E0(spritetype *, XSPRITE *);
+
+static int nAttackClient = seqRegisterClient(SeqAttackCallback);
+
+AISTATE tinycalebIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE tinycalebChase = { kAiStateChase, 6, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE tinycalebDodge = { kAiStateMove, 6, -1, 90, NULL, aiMoveDodge, NULL, &tinycalebChase };
+AISTATE tinycalebGoto = { kAiStateMove, 6, -1, 600, NULL, aiMoveForward, thinkGoto, &tinycalebIdle };
+AISTATE tinycalebAttack = { kAiStateChase, 0, nAttackClient, 120, NULL, NULL, NULL, &tinycalebChase };
+AISTATE tinycalebSearch = { kAiStateSearch, 6, -1, 120, NULL, aiMoveForward, thinkSearch, &tinycalebIdle };
+AISTATE tinycalebRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &tinycalebDodge };
+AISTATE tinycalebTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &tinycalebDodge };
+AISTATE tinycalebSwimIdle = { kAiStateIdle, 10, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE tinycalebSwimChase = { kAiStateChase, 8, -1, 0, NULL, sub_65D04, thinkSwimChase, NULL };
+AISTATE tinycalebSwimDodge = { kAiStateMove, 8, -1, 90, NULL, aiMoveDodge, NULL, &tinycalebSwimChase };
+AISTATE tinycalebSwimGoto = { kAiStateMove, 8, -1, 600, NULL, aiMoveForward, thinkSwimGoto, &tinycalebSwimIdle };
+AISTATE tinycalebSwimSearch = { kAiStateSearch, 8, -1, 120, NULL, aiMoveForward, thinkSearch, &tinycalebSwimIdle };
+AISTATE tinycalebSwimAttack = { kAiStateChase, 10, nAttackClient, 0, NULL, NULL, NULL, &tinycalebSwimChase };
+AISTATE tinycalebSwimRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &tinycalebSwimDodge };
+AISTATE tinycaleb139660 = { kAiStateOther, 8, -1, 120, NULL, sub_65F44, thinkSwimChase, &tinycalebSwimChase };
+AISTATE tinycaleb13967C = { kAiStateOther, 8, -1, 0, NULL, sub_661E0, thinkSwimChase, &tinycalebSwimChase };
+AISTATE tinycaleb139698 = { kAiStateOther, 8, -1, 120, NULL, aiMoveTurn, NULL, &tinycalebSwimChase };
+
+static void SeqAttackCallback(int, int nXSprite)
+{
+ int nSprite = xsprite[nXSprite].reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ int dz = gDudeSlope[nXSprite];
+ dx += Random2(1500);
+ dy += Random2(1500);
+ dz += Random2(1500);
+ for (int i = 0; i < 2; i++)
+ {
+ int r1 = Random3(500);
+ int r2 = Random3(1000);
+ int r3 = Random3(1000);
+ actFireVector(pSprite, 0, 0, dx+r3, dy+r2, dz+r1, VECTOR_TYPE_1);
+ }
+ if (Chance(0x8000))
+ sfxPlay3DSound(pSprite, 10000+Random(5), -1, 0);
+ if (Chance(0x8000))
+ sfxPlay3DSound(pSprite, 1001, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1002, -1, 0);
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebSearch);
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebSearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ else
+ {
+ aiPlay3DSound(pSprite, 11000+Random(4), AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &tinycalebSearch);
+ }
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int nXSprite = sprite[pXSprite->reference].extra;
+ gDudeSlope[nXSprite] = divscale(pTarget->z-pSprite->z, nDist, 10);
+ if (nDist < 0x599 && klabs(nDeltaAngle) < 28)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimAttack);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebAttack);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimAttack);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebAttack);
+ }
+ else
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimDodge);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebDodge);
+ }
+ break;
+ default:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimAttack);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebAttack);
+ break;
+ }
+ }
+ }
+ return;
+ }
+ }
+
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimGoto);
+ else
+ aiNewState(pSprite, pXSprite, &tinycalebGoto);
+ if (Chance(0x2000))
+ sfxPlay3DSound(pSprite, 10000 + Random(5), -1, 0);
+ pXSprite->target = -1;
+}
+
+static void thinkSwimGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkSwimChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &tinycalebSwimGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &tinycalebSwimSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = pDudeInfo->eyeHeight+pSprite->z;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int UNUSED(floorZ) = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &tinycalebSwimAttack);
+ else
+ aiNewState(pSprite, pXSprite, &tinycaleb13967C);
+ }
+ }
+ return;
+ }
+ aiNewState(pSprite, pXSprite, &tinycalebSwimGoto);
+ pXSprite->target = -1;
+}
+
+static void sub_65D04(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Random(64) < 32 && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>2;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void sub_65F44(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int z = pSprite->z + dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+ int z2 = pTarget->z + dudeInfo[pTarget->type - kDudeBase].eyeHeight;
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int dz = z2 - z;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -dz;
+}
+
+static void sub_661E0(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int z = pSprite->z + dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+ int z2 = pTarget->z + dudeInfo[pTarget->type - kDudeBase].eyeHeight;
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pSprite->ang = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int dz = (z2 - z)<<3;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = dz;
+}
diff --git a/source/blood/src/aicaleb.h b/source/blood/src/aicaleb.h
new file mode 100644
index 000000000..969070bda
--- /dev/null
+++ b/source/blood/src/aicaleb.h
@@ -0,0 +1,43 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE tinycalebIdle;
+extern AISTATE tinycalebChase;
+extern AISTATE tinycalebDodge;
+extern AISTATE tinycalebGoto;
+extern AISTATE tinycalebAttack;
+extern AISTATE tinycalebSearch;
+extern AISTATE tinycalebRecoil;
+extern AISTATE tinycalebTeslaRecoil;
+extern AISTATE tinycalebSwimIdle;
+extern AISTATE tinycalebSwimChase;
+extern AISTATE tinycalebSwimDodge;
+extern AISTATE tinycalebSwimGoto;
+extern AISTATE tinycalebSwimSearch;
+extern AISTATE tinycalebSwimAttack;
+extern AISTATE tinycalebSwimRecoil;
+extern AISTATE tinycaleb139660;
+extern AISTATE tinycaleb13967C;
+extern AISTATE tinycaleb139698;
diff --git a/source/blood/src/aicerber.cpp b/source/blood/src/aicerber.cpp
new file mode 100644
index 000000000..8d509bf9b
--- /dev/null
+++ b/source/blood/src/aicerber.cpp
@@ -0,0 +1,489 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aicerber.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void BiteSeqCallback(int, int);
+static void BurnSeqCallback(int, int);
+static void BurnSeqCallback2(int, int);
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite);
+static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite);
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite);
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite);
+
+static int nBiteClient = seqRegisterClient(BiteSeqCallback);
+static int nBurnClient = seqRegisterClient(BurnSeqCallback);
+static int nBurnClient2 = seqRegisterClient(BurnSeqCallback2);
+
+AISTATE cerberusIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE cerberusSearch = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &cerberusIdle };
+AISTATE cerberusChase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cerberusRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cerberusSearch };
+AISTATE cerberusTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &cerberusSearch };
+AISTATE cerberusGoto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &cerberusIdle };
+AISTATE cerberusBite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &cerberusChase };
+AISTATE cerberusBurn = { kAiStateChase, 6, nBurnClient, 60, NULL, NULL, NULL, &cerberusChase };
+AISTATE cerberus3Burn = { kAiStateChase, 6, nBurnClient2, 60, NULL, NULL, NULL, &cerberusChase };
+AISTATE cerberus2Idle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE cerberus2Search = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &cerberus2Idle };
+AISTATE cerberus2Chase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cerberus2Recoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cerberus2Search };
+AISTATE cerberus2Goto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &cerberus2Idle };
+AISTATE cerberus2Bite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &cerberus2Chase };
+AISTATE cerberus2Burn = { kAiStateChase, 6, nBurnClient, 60, NULL, NULL, NULL, &cerberus2Chase };
+AISTATE cerberus4Burn = { kAiStateChase, 6, nBurnClient2, 60, NULL, NULL, NULL, &cerberus2Chase };
+AISTATE cerberus139890 = { kAiStateOther, 7, -1, 120, NULL, aiMoveTurn, NULL, &cerberusChase };
+AISTATE cerberus1398AC = { kAiStateOther, 7, -1, 120, NULL, aiMoveTurn, NULL, &cerberusChase };
+
+static void BiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int dz = pTarget->z-pSprite->z;
+ actFireVector(pSprite, 350, -100, dx, dy, dz, VECTOR_TYPE_14);
+ actFireVector(pSprite, -350, 0, dx, dy, dz, VECTOR_TYPE_14);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_14);
+}
+
+static void BurnSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int height = pDudeInfo->eyeHeight*pSprite->yrepeat;
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = height; // ???
+ TARGETTRACK tt1 = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
+ Aim aim;
+ aim.dx = Cos(pSprite->ang)>>16;
+ aim.dy = Sin(pSprite->ang)>>16;
+ aim.dz = gDudeSlope[nXSprite];
+ int nClosest = 0x7fffffff;
+ for (short nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite == pSprite2 || !(pSprite2->hitag&8))
+ continue;
+ int x2 = pSprite2->x;
+ int y2 = pSprite2->y;
+ int z2 = pSprite2->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 0x2800)
+ continue;
+ if (tt1.at10)
+ {
+ int t = divscale(nDist, tt1.at10, 12);
+ x2 += (xvel[nSprite2]*t)>>12;
+ y2 += (yvel[nSprite2]*t)>>12;
+ z2 += (zvel[nSprite2]*t)>>8;
+ }
+ int tx = x+mulscale30(Cos(pSprite->ang), nDist);
+ int ty = y+mulscale30(Sin(pSprite->ang), nDist);
+ int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
+ int tsr = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (tz-tsr > bottom || tz+tsr < top)
+ continue;
+ int dx = (tx-x2)>>4;
+ int dy = (ty-y2)>>4;
+ int dz = (tz-z2)>>8;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 < nClosest)
+ {
+ int nAngle = getangle(x2-x, y2-y);
+ int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
+ if (klabs(nDeltaAngle) <= tt1.at8)
+ {
+ int tz = pSprite2->z-pSprite->z;
+ if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(nAngle)>>16;
+ aim.dy = Sin(nAngle)>>16;
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ else
+ aim.dz = tz;
+ }
+ }
+ }
+ switch (pSprite->type)
+ {
+ case 227:
+ actFireMissile(pSprite, -350, 0, aim.dx, aim.dy, aim.dz, 313);
+ actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, aim.dz, 313);
+ break;
+ case 228:
+ actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, aim.dz, 313);
+ break;
+ }
+}
+
+static void BurnSeqCallback2(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int height = pDudeInfo->eyeHeight*pSprite->yrepeat;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = height; // ???
+ TARGETTRACK tt1 = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
+ Aim aim;
+ int ax, ay, az;
+ aim.dx = ax = Cos(pSprite->ang)>>16;
+ aim.dy = ay = Sin(pSprite->ang)>>16;
+ aim.dz = gDudeSlope[nXSprite];
+ az = 0;
+ int nClosest = 0x7fffffff;
+ for (short nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite == pSprite2 || !(pSprite2->hitag&8))
+ continue;
+ int x2 = pSprite2->x;
+ int y2 = pSprite2->y;
+ int z2 = pSprite2->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 0x2800)
+ continue;
+ if (tt1.at10)
+ {
+ int t = divscale(nDist, tt1.at10, 12);
+ x2 += (xvel[nSprite2]*t)>>12;
+ y2 += (yvel[nSprite2]*t)>>12;
+ z2 += (zvel[nSprite2]*t)>>8;
+ }
+ int tx = x+mulscale30(Cos(pSprite->ang), nDist);
+ int ty = y+mulscale30(Sin(pSprite->ang), nDist);
+ int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
+ int tsr = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (tz-tsr > bottom || tz+tsr < top)
+ continue;
+ int dx = (tx-x2)>>4;
+ int dy = (ty-y2)>>4;
+ int dz = (tz-z2)>>8;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 < nClosest)
+ {
+ int nAngle = getangle(x2-x, y2-y);
+ int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
+ if (klabs(nDeltaAngle) <= tt1.at8)
+ {
+ DUDEINFO *pDudeInfo2 = &dudeInfo[pSprite2->type - kDudeBase];
+ int height = (pDudeInfo2->aimHeight*pSprite2->yrepeat)<<2;
+ int tz = (z2-height)-z;
+ if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(nAngle)>>16;
+ aim.dy = Sin(nAngle)>>16;
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ else
+ aim.dz = tz;
+ }
+ }
+ }
+ switch (pSprite->type)
+ {
+ case 227:
+ actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, -aim.dz, 308);
+ actFireMissile(pSprite, -350, 0, ax, ay, az, 308);
+ break;
+ case 228:
+ actFireMissile(pSprite, 350, -100, aim.dx, aim.dy, -aim.dz, 308);
+ break;
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
+ pDudeExtraE->at4++;
+ else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
+ {
+ pXSprite->goalAng += 256;
+ POINT3D *pTarget = &baseSprite[pSprite->index];
+ aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
+ if (pSprite->type == 227)
+ aiNewState(pSprite, pXSprite, &cerberus139890);
+ else
+ aiNewState(pSprite, pXSprite, &cerberus1398AC);
+ return;
+ }
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ pDudeExtraE->at0 = 0;
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ pDudeExtraE->at0 = 0;
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else
+ continue;
+ break;
+ }
+ }
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberusSearch);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus2Search);
+ break;
+ }
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberusGoto);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus2Goto);
+ break;
+ }
+ return;
+ }
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberusSearch);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus2Search);
+ break;
+ }
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberusSearch);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus2Search);
+ break;
+ }
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x1b00 && nDist > 0xd00 && klabs(nDeltaAngle) < 85)
+ {
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberusBurn);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus2Burn);
+ break;
+ }
+ }
+ else if (nDist < 0xb00 && nDist > 0x500 && klabs(nDeltaAngle) < 85)
+ {
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberus3Burn);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus4Burn);
+ break;
+ }
+ }
+ else if (nDist < 0x200 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (pSprite->type)
+ {
+ case 227:
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &cerberusBite);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 211)
+ aiNewState(pSprite, pXSprite, &cerberusBite);
+ break;
+ case 0:
+ case 4:
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cerberusBite);
+ break;
+ }
+ break;
+ case 228:
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &cerberus2Bite);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 211)
+ aiNewState(pSprite, pXSprite, &cerberus2Bite);
+ break;
+ case 0:
+ case 4:
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cerberus2Bite);
+ break;
+ }
+ break;
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ switch (pSprite->type)
+ {
+ case 227:
+ aiNewState(pSprite, pXSprite, &cerberusGoto);
+ break;
+ case 228:
+ aiNewState(pSprite, pXSprite, &cerberus2Goto);
+ break;
+ }
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aicerber.h b/source/blood/src/aicerber.h
new file mode 100644
index 000000000..5c416e31a
--- /dev/null
+++ b/source/blood/src/aicerber.h
@@ -0,0 +1,44 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE cerberusIdle;
+extern AISTATE cerberusSearch;
+extern AISTATE cerberusChase;
+extern AISTATE cerberusRecoil;
+extern AISTATE cerberusTeslaRecoil;
+extern AISTATE cerberusGoto;
+extern AISTATE cerberusBite;
+extern AISTATE cerberusBurn;
+extern AISTATE cerberus3Burn;
+extern AISTATE cerberus2Idle;
+extern AISTATE cerberus2Search;
+extern AISTATE cerberus2Chase;
+extern AISTATE cerberus2Recoil;
+extern AISTATE cerberus2Goto;
+extern AISTATE cerberus2Bite;
+extern AISTATE cerberus2Burn;
+extern AISTATE cerberus4Burn;
+extern AISTATE cerberus139890;
+extern AISTATE cerberus1398AC;
diff --git a/source/blood/src/aicult.cpp b/source/blood/src/aicult.cpp
new file mode 100644
index 000000000..b64311e43
--- /dev/null
+++ b/source/blood/src/aicult.cpp
@@ -0,0 +1,659 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aicult.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void TommySeqCallback(int, int);
+static void TeslaSeqCallback(int, int);
+static void ShotSeqCallback(int, int);
+static void ThrowSeqCallback(int, int);
+static void sub_68170(int, int);
+static void sub_68230(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+
+static int nTommyClient = seqRegisterClient(TommySeqCallback);
+static int nTeslaClient = seqRegisterClient(TeslaSeqCallback);
+static int nShotClient = seqRegisterClient(ShotSeqCallback);
+static int nThrowClient = seqRegisterClient(ThrowSeqCallback);
+static int n68170Client = seqRegisterClient(sub_68170);
+static int n68230Client = seqRegisterClient(sub_68230);
+
+AISTATE cultistIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE cultistProneIdle = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE fanaticProneIdle = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE cultistProneIdle3 = { kAiStateIdle, 17, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE cultistChase = { kAiStateChase, 9, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE fanaticChase = { kAiStateChase, 0, -1, 0, NULL, aiMoveTurn, thinkChase, NULL };
+AISTATE cultistDodge = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &cultistChase };
+AISTATE cultistGoto = { kAiStateMove, 9, -1, 600, NULL, aiMoveForward, thinkGoto, &cultistIdle };
+AISTATE cultistProneChase = { kAiStateChase, 14, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cultistProneDodge = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge, NULL, &cultistProneChase };
+AISTATE cultistTThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistTFire };
+AISTATE cultistSThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistSFire };
+AISTATE cultistTsThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistTsFire };
+AISTATE cultistDThrow = { kAiStateChase, 7, nThrowClient, 120, NULL, NULL, NULL, &cultistChase };
+AISTATE cultist139A78 = { kAiStateChase, 7, n68170Client, 120, NULL, NULL, NULL, &cultistChase };
+AISTATE cultist139A94 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, NULL, &cultistIdle };
+AISTATE cultist139AB0 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139A94 };
+AISTATE cultist139ACC = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139AB0 };
+AISTATE cultist139AE8 = { kAiStateChase, 7, n68230Client, 120, NULL, NULL, thinkSearch, &cultist139AE8 };
+AISTATE cultistSearch = { kAiStateSearch, 9, -1, 1800, NULL, aiMoveForward, thinkSearch, &cultistIdle };
+AISTATE cultistSFire = { kAiStateChase, 6, nShotClient, 60, NULL, NULL, NULL, &cultistChase };
+AISTATE cultistTFire = { kAiStateChase, 6, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTFire };
+AISTATE cultistTsFire = { kAiStateChase, 6, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistChase };
+AISTATE cultistSProneFire = { kAiStateChase, 8, nShotClient, 60, NULL, NULL, NULL, &cultistProneChase };
+AISTATE cultistTProneFire = { kAiStateChase, 8, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTProneFire };
+AISTATE cultistTsProneFire = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, NULL, &cultistTsProneFire };
+AISTATE cultistRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistDodge };
+AISTATE cultistProneRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistProneDodge };
+AISTATE cultistTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &cultistDodge };
+AISTATE cultistSwimIdle = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE cultistSwimChase = { kAiStateChase, 13, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE cultistSwimDodge = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge, NULL, &cultistSwimChase };
+AISTATE cultistSwimGoto = { kAiStateMove, 13, -1, 600, NULL, aiMoveForward, thinkGoto, &cultistSwimIdle };
+AISTATE cultistSwimSearch = { kAiStateSearch, 13, -1, 1800, NULL, aiMoveForward, thinkSearch, &cultistSwimIdle };
+AISTATE cultistSSwimFire = { kAiStateChase, 8, nShotClient, 60, NULL, NULL, NULL, &cultistSwimChase };
+AISTATE cultistTSwimFire = { kAiStateChase, 8, nTommyClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTSwimFire };
+AISTATE cultistTsSwimFire = { kAiStateChase, 8, nTeslaClient, 0, NULL, aiMoveTurn, thinkChase, &cultistTsSwimFire };
+AISTATE cultistSwimRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &cultistSwimDodge };
+
+static void TommySeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = gDudeSlope[nXSprite];
+ dx += Random3((5-gGameOptions.nDifficulty)*1000);
+ dy += Random3((5-gGameOptions.nDifficulty)*1000);
+ dz += Random3((5-gGameOptions.nDifficulty)*500);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_2);
+ sfxPlay3DSound(pSprite, 4001, -1, 0);
+}
+
+static void TeslaSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ if (Chance(dword_138BB0[gGameOptions.nDifficulty]))
+ {
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = gDudeSlope[nXSprite];
+ dx += Random3((5-gGameOptions.nDifficulty)*1000);
+ dy += Random3((5-gGameOptions.nDifficulty)*1000);
+ dz += Random3((5-gGameOptions.nDifficulty)*500);
+ actFireMissile(pSprite, 0, 0, dx, dy, dz, 306);
+ sfxPlay3DSound(pSprite, 470, -1, 0);
+ }
+}
+
+static void ShotSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int dz = gDudeSlope[nXSprite];
+ dx += Random2((5-gGameOptions.nDifficulty)*1000-500);
+ dy += Random2((5-gGameOptions.nDifficulty)*1000-500);
+ dz += Random2((5-gGameOptions.nDifficulty)*500);
+ for (int i = 0; i < 8; i++)
+ {
+ int r1 = Random3(500);
+ int r2 = Random3(1000);
+ int r3 = Random3(1000);
+ actFireVector(pSprite, 0, 0, dx+r3, dy+r2, dz+r1, VECTOR_TYPE_1);
+ }
+ if (Chance(0x8000))
+ sfxPlay3DSound(pSprite, 1001, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 1002, -1, 0);
+}
+
+static void ThrowSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int nMissile = 418;
+ if (gGameOptions.nDifficulty > 2)
+ nMissile = 419;
+ char v4 = Chance(0x6000);
+ sfxPlay3DSound(pSprite, 455, -1, 0);
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ int dx = pTarget->x - pSprite->x;
+ int dy = pTarget->y - pSprite->y;
+ int dz = pTarget->z - pSprite->z;
+ int nDist = approxDist(dx, dy);
+ int nDist2 = nDist / 540;
+ if (nDist > 0x1e00)
+ v4 = 0;
+ spritetype *pMissile = actFireThing(pSprite, 0, 0, dz/128-14500, nMissile, (nDist2<<23)/120);
+ if (v4)
+ xsprite[pMissile->extra].Impact = 1;
+ else
+ evPost(pMissile->index, 3, 120*(1+Random(2)), COMMAND_ID_1);
+}
+
+static void sub_68170(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int nMissile = 418;
+ if (gGameOptions.nDifficulty > 2)
+ nMissile = 419;
+ sfxPlay3DSound(pSprite, 455, -1, 0);
+ spritetype *pMissile = actFireThing(pSprite, 0, 0, gDudeSlope[nXSprite]-9460, nMissile, 0x133333);
+ evPost(pMissile->index, 3, 120*(2+Random(2)), COMMAND_ID_1);
+}
+
+static void sub_68230(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int nMissile = 418;
+ if (gGameOptions.nDifficulty > 2)
+ nMissile = 419;
+ sfxPlay3DSound(pSprite, 455, -1, 0);
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ int dx = pTarget->x - pSprite->x;
+ int dy = pTarget->y - pSprite->y;
+ int dz = pTarget->z - pSprite->z;
+ int nDist = approxDist(dx, dy);
+ int nDist2 = nDist / 540;
+ spritetype *pMissile = actFireThing(pSprite, 0, 0, dz/128-14500, nMissile, (nDist2<<17)/120);
+ xsprite[pMissile->extra].Impact = 1;
+}
+
+static char TargetNearExplosion(spritetype *pSprite)
+{
+ for (short nSprite = headspritesect[pSprite->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ if (sprite[nSprite].type == 418 || sprite[nSprite].statnum == 2)
+ return 1;
+ }
+ return 0;
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ sub_5F15C(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 5120 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimSearch);
+ break;
+ }
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistGoto);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ break;
+ }
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ if (pSprite->type == 201)
+ aiPlay3DSound(pSprite, 4021+Random(4), AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1021+Random(4), AI_SFX_PRIORITY_1, -1);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimSearch);
+ break;
+ }
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistSearch);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimSearch);
+ break;
+ }
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int nXSprite = sprite[pXSprite->reference].extra;
+ gDudeSlope[nXSprite] = divscale(pTarget->z-pSprite->z, nDist, 10);
+ switch (pSprite->type)
+ {
+ case 201:
+ if (nDist < 0x1e00 && nDist > 0xe00 && klabs(nDeltaAngle) < 85 && !TargetNearExplosion(pTarget)
+ && (pTarget->hitag&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].at2e
+ && Chance(0x8000))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistTThrow);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202 && pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistTThrow);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cultistTThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x4600 && klabs(nDeltaAngle) < 28)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTProneFire);
+ else if (sub_5BDA8(pSprite, 13) && (pXSprite->medium == 1 || pXSprite->medium == 2))
+ aiNewState(pSprite, pXSprite, &cultistTSwimFire);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202)
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistTSwimFire);
+ }
+ else
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistDodge);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistProneDodge);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSwimDodge);
+ }
+ break;
+ default:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistTSwimFire);
+ break;
+ }
+ }
+ break;
+ case 202:
+ if (nDist < 0x2c00 && nDist > 0x1400 && !TargetNearExplosion(pTarget)
+ && (pTarget->hitag&2) && gGameOptions.nDifficulty >= 2 && IsPlayerSprite(pTarget) && !gPlayer[pTarget->type-kDudePlayer1].at2e
+ && Chance(0x8000))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202 && pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSSwimFire);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 201)
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSSwimFire);
+ }
+ else
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistDodge);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistProneDodge);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSwimDodge);
+ }
+ break;
+ default:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSSwimFire);
+ break;
+ }
+ }
+ break;
+ case 247:
+ if (nDist < 0x1e00 && nDist > 0xe00 && !TargetNearExplosion(pTarget)
+ && (pTarget->hitag&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].at2e
+ && Chance(0x8000))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistTsThrow);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202 && pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistTsThrow);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cultistTsThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTsFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTsProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 201)
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTsFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTsProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
+ }
+ else
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistDodge);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistProneDodge);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSwimDodge);
+ }
+ break;
+ default:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTsFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistTsProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistTsSwimFire);
+ break;
+ }
+ }
+ break;
+ case 248:
+ if (nDist < 0x2c00 && nDist > 0x1400 && klabs(nDeltaAngle) < 85
+ && (pTarget->hitag&2) && IsPlayerSprite(pTarget))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistDThrow);
+ break;
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202 && pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistDThrow);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cultistDThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x1400 && klabs(nDeltaAngle) < 85
+ && (pTarget->hitag&2) && IsPlayerSprite(pTarget))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultist139A78);
+ break;
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202 && pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultist139A78);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cultist139A78);
+ break;
+ }
+ }
+ break;
+ case 249:
+ if (nDist < 0x1e00 && nDist > 0xe00 && !TargetNearExplosion(pTarget)
+ && (pTarget->hitag&2) && gGameOptions.nDifficulty > 2 && IsPlayerSprite(pTarget) && gPlayer[pTarget->type-kDudePlayer1].at2e
+ && Chance(0x8000))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 202 && pXSprite->medium != 1 && pXSprite->medium != 2)
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &cultistSThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x3200 && klabs(nDeltaAngle) < 28)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSSwimFire);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 201)
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSSwimFire);
+ }
+ else
+ {
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistDodge);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistProneDodge);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSwimDodge);
+ }
+ break;
+ default:
+ if (!sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSFire);
+ else if (sub_5BDA8(pSprite, 14) && pXSprite->medium == 0)
+ aiNewState(pSprite, pXSprite, &cultistSProneFire);
+ else if (pXSprite->medium == 1 || pXSprite->medium == 2)
+ aiNewState(pSprite, pXSprite, &cultistSSwimFire);
+ break;
+ }
+ }
+ break;
+ }
+ return;
+ }
+ }
+ }
+ switch (pXSprite->medium)
+ {
+ case 0:
+ aiNewState(pSprite, pXSprite, &cultistGoto);
+ break;
+ case 1:
+ case 2:
+ aiNewState(pSprite, pXSprite, &cultistSwimGoto);
+ break;
+ }
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aicult.h b/source/blood/src/aicult.h
new file mode 100644
index 000000000..0a09c3deb
--- /dev/null
+++ b/source/blood/src/aicult.h
@@ -0,0 +1,63 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE cultistIdle;
+extern AISTATE cultistProneIdle;
+extern AISTATE fanaticProneIdle;
+extern AISTATE cultistProneIdle3;
+extern AISTATE cultistChase;
+extern AISTATE fanaticChase;
+extern AISTATE cultistDodge;
+extern AISTATE cultistGoto;
+extern AISTATE cultistProneChase;
+extern AISTATE cultistProneDodge;
+extern AISTATE cultistTThrow;
+extern AISTATE cultistSThrow;
+extern AISTATE cultistTsThrow;
+extern AISTATE cultistDThrow;
+extern AISTATE cultist139A78;
+extern AISTATE cultist139A94;
+extern AISTATE cultist139AB0;
+extern AISTATE cultist139ACC;
+extern AISTATE cultist139AE8;
+extern AISTATE cultistSearch;
+extern AISTATE cultistSFire;
+extern AISTATE cultistTFire;
+extern AISTATE cultistTsFire;
+extern AISTATE cultistSProneFire;
+extern AISTATE cultistTProneFir;
+extern AISTATE cultistTsProneFire;
+extern AISTATE cultistRecoil;
+extern AISTATE cultistProneRecoil;
+extern AISTATE cultistTeslaRecoil;
+extern AISTATE cultistSwimIdle;
+extern AISTATE cultistSwimChase;
+extern AISTATE cultistSwimDodge;
+extern AISTATE cultistSwimGoto;
+extern AISTATE cultistSwimSearch;
+extern AISTATE cultistSSwimFire;
+extern AISTATE cultistTSwimFire;
+extern AISTATE cultistTsSwimFire;
+extern AISTATE cultistSwimRecoil;
diff --git a/source/blood/src/aigarg.cpp b/source/blood/src/aigarg.cpp
new file mode 100644
index 000000000..ddb7042dd
--- /dev/null
+++ b/source/blood/src/aigarg.cpp
@@ -0,0 +1,704 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aigarg.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void SlashFSeqCallback(int, int);
+static void ThrowFSeqCallback(int, int);
+static void BlastSSeqCallback(int, int);
+static void ThrowSSeqCallback(int, int);
+static void thinkTarget(spritetype *, XSPRITE *);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void MoveDodgeUp(spritetype *, XSPRITE *);
+static void MoveDodgeDown(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void entryFStatue(spritetype *, XSPRITE *);
+static void entrySStatue(spritetype *, XSPRITE *);
+static void MoveForward(spritetype *, XSPRITE *);
+static void MoveSlow(spritetype *, XSPRITE *);
+static void MoveSwoop(spritetype *, XSPRITE *);
+static void MoveFly(spritetype *, XSPRITE *);
+static void playStatueBreakSnd(spritetype*,XSPRITE*);
+
+static int nSlashFClient = seqRegisterClient(SlashFSeqCallback);
+static int nThrowFClient = seqRegisterClient(ThrowFSeqCallback);
+static int nThrowSClient = seqRegisterClient(ThrowSSeqCallback);
+static int nBlastSClient = seqRegisterClient(BlastSSeqCallback);
+
+AISTATE gargoyleFIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE gargoyleStatueIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, NULL, NULL };
+AISTATE gargoyleFChase = { kAiStateChase, 0, -1, 0, NULL, MoveForward, thinkChase, &gargoyleFIdle };
+AISTATE gargoyleFGoto = { kAiStateMove, 0, -1, 600, NULL, MoveForward, thinkGoto, &gargoyleFIdle };
+AISTATE gargoyleFSlash = { kAiStateChase, 6, nSlashFClient, 120, NULL, NULL, NULL, &gargoyleFChase };
+AISTATE gargoyleFThrow = { kAiStateChase, 6, nThrowFClient, 120, NULL, NULL, NULL, &gargoyleFChase };
+AISTATE gargoyleSThrow = { kAiStateChase, 6, nThrowSClient, 120, NULL, MoveForward, NULL, &gargoyleFChase };
+AISTATE gargoyleSBlast = { kAiStateChase, 7, nBlastSClient, 60, NULL, MoveSlow, NULL, &gargoyleFChase };
+AISTATE gargoyleFRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &gargoyleFChase };
+AISTATE gargoyleFSearch = { kAiStateSearch, 0, -1, 120, NULL, MoveForward, thinkSearch, &gargoyleFIdle };
+AISTATE gargoyleFMorph2 = { kAiStateOther, -1, -1, 0, entryFStatue, NULL, NULL, &gargoyleFIdle };
+AISTATE gargoyleFMorph = { kAiStateOther, 6, -1, 0, NULL, NULL, NULL, &gargoyleFMorph2 };
+AISTATE gargoyleSMorph2 = { kAiStateOther, -1, -1, 0, entrySStatue, NULL, NULL, &gargoyleFIdle };
+AISTATE gargoyleSMorph = { kAiStateOther, 6, -1, 0, NULL, NULL, NULL, &gargoyleSMorph2 };
+AISTATE gargoyleSwoop = { kAiStateOther, 0, -1, 120, NULL, MoveSwoop, thinkChase, &gargoyleFChase };
+AISTATE gargoyleFly = { kAiStateMove, 0, -1, 120, NULL, MoveFly, thinkChase, &gargoyleFChase };
+AISTATE gargoyleTurn = { kAiStateMove, 0, -1, 120, NULL, aiMoveTurn, NULL, &gargoyleFChase };
+AISTATE gargoyleDodgeUp = { kAiStateMove, 0, -1, 60, NULL, MoveDodgeUp, NULL, &gargoyleFChase };
+AISTATE gargoyleFDodgeUpRight = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeUp, NULL, &gargoyleFChase };
+AISTATE gargoyleFDodgeUpLeft = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeUp, NULL, &gargoyleFChase };
+AISTATE gargoyleDodgeDown = { kAiStateMove, 0, -1, 120, NULL, MoveDodgeDown, NULL, &gargoyleFChase };
+AISTATE gargoyleFDodgeDownRight = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeDown, NULL, &gargoyleFChase };
+AISTATE gargoyleFDodgeDownLeft = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeDown, NULL, &gargoyleFChase };
+
+AISTATE statueFBreakSEQ = { kAiStateOther, 5, -1, 0, entryFStatue, NULL, playStatueBreakSnd, &gargoyleFMorph2};
+AISTATE statueSBreakSEQ = { kAiStateOther, 5, -1, 0, entrySStatue, NULL, playStatueBreakSnd, &gargoyleSMorph2};
+
+static void playStatueBreakSnd(spritetype* pSprite, XSPRITE* pXSprite) {
+ UNREFERENCED_PARAMETER(pXSprite);
+ aiPlay3DSound(pSprite, 313, AI_SFX_PRIORITY_1, -1);
+}
+
+static void SlashFSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type - kDudeBase];
+ int height = (pSprite->yrepeat*pDudeInfo->eyeHeight)<<2;
+ int height2 = (pTarget->yrepeat*pDudeInfoT->eyeHeight)<<2;
+ int dz = height-height2;
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_13);
+ int r1 = Random(50);
+ int r2 = Random(50);
+ actFireVector(pSprite, 0, 0, dx+r2, dy-r1, dz, VECTOR_TYPE_13);
+ r1 = Random(50);
+ r2 = Random(50);
+ actFireVector(pSprite, 0, 0, dx-r2, dy+r1, dz, VECTOR_TYPE_13);
+}
+
+static void ThrowFSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ actFireThing(&sprite[nSprite], 0, 0, gDudeSlope[nXSprite]-7500, 421, 0xeeeee);
+}
+
+static void BlastSSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ wrand(); // ???
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int height = (pSprite->yrepeat*dudeInfo[pSprite->type-kDudeBase].eyeHeight) << 2;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nDist) = approxDist(dx, dy);
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = height;
+ TARGETTRACK tt = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
+ Aim aim;
+ aim.dx = Cos(pSprite->ang)>>16;
+ aim.dy = Sin(pSprite->ang)>>16;
+ aim.dz = gDudeSlope[nXSprite];
+ int nClosest = 0x7fffffff;
+ for (short nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite == pSprite2 || !(pSprite2->hitag&8))
+ continue;
+ int x2 = pSprite2->x;
+ int y2 = pSprite2->y;
+ int z2 = pSprite2->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 0x2800)
+ continue;
+ if (tt.at10)
+ {
+ int t = divscale(nDist, tt.at10, 12);
+ x2 += (xvel[nSprite2]*t)>>12;
+ y2 += (yvel[nSprite2]*t)>>12;
+ z2 += (zvel[nSprite2]*t)>>8;
+ }
+ int tx = x+mulscale30(Cos(pSprite->ang), nDist);
+ int ty = y+mulscale30(Sin(pSprite->ang), nDist);
+ int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
+ int tsr = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (tz-tsr > bottom || tz+tsr < top)
+ continue;
+ int dx = (tx-x2)>>4;
+ int dy = (ty-y2)>>4;
+ int dz = (tz-z2)>>8;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 < nClosest)
+ {
+ int nAngle = getangle(x2-x, y2-y);
+ int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
+ if (klabs(nDeltaAngle) <= tt.at8)
+ {
+ int tz = pSprite2->z-pSprite->z;
+ if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(nAngle)>>16;
+ aim.dy = Sin(nAngle)>>16;
+ aim.dz = divscale(tz, nDist, 10);
+ if (tz > -0x333)
+ aim.dz = divscale(tz, nDist, 10);
+ else if (tz < -0x333 && tz > -0xb33)
+ aim.dz = divscale(tz, nDist, 10)+9460;
+ else if (tz < -0xb33 && tz > -0x3000)
+ aim.dz = divscale(tz, nDist, 10)+9460;
+ else if (tz < -0x3000)
+ aim.dz = divscale(tz, nDist, 10)-7500;
+ else
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ else
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ }
+ }
+ if (IsPlayerSprite(pTarget) || !VanillaMode()) // By NoOne: allow to fire missile in non-player targets
+ {
+ actFireMissile(pSprite, -120, 0, aim.dx, aim.dy, aim.dz, 311);
+ actFireMissile(pSprite, 120, 0, aim.dx, aim.dy, aim.dz, 311);
+ }
+}
+
+static void ThrowSSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ actFireThing(pSprite, 0, 0, gDudeSlope[nXSprite]-7500, 421, Chance(0x6000) ? 0x133333 : 0x111111);
+}
+
+static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
+ pDudeExtraE->at4++;
+ else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
+ {
+ pXSprite->goalAng += 256;
+ POINT3D *pTarget = &baseSprite[pSprite->index];
+ aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
+ aiNewState(pSprite, pXSprite, &gargoyleTurn);
+ return;
+ }
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ pDudeExtraE->at4 = 0;
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ pDudeExtraE->at4 = 0;
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else
+ continue;
+ break;
+ }
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ sub_5F15C(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void MoveDodgeUp(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -0x1d555;
+}
+
+static void MoveDodgeDown(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (pXSprite->dodgeDir == 0)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = 0x44444;
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFGoto);
+ return;
+ }
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ // Should be dudeInfo[pTarget->type-kDudeBase]
+ int height2 = (pDudeInfo->eyeHeight*pTarget->yrepeat)<<2;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int floorZ = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ switch (pSprite->type)
+ {
+ case 206:
+ if (nDist < 0x1800 && nDist > 0xc00 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ sfxPlay3DSound(pSprite, 1408, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleFThrow);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 207)
+ {
+ sfxPlay3DSound(pSprite, 1408, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleFThrow);
+ }
+ break;
+ default:
+ sfxPlay3DSound(pSprite, 1408, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleFThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ sfxPlay3DSound(pSprite, 1406, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 207)
+ {
+ sfxPlay3DSound(pSprite, 1406, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ }
+ break;
+ default:
+ sfxPlay3DSound(pSprite, 1406, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ break;
+ }
+ }
+ else if ((height2-height > 0x2000 || floorZ-bottom > 0x2000) && nDist < 0x1400 && nDist > 0xa00)
+ {
+ aiPlay3DSound(pSprite, 1400, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &gargoyleSwoop);
+ }
+ else if ((height2-height < 0x2000 || floorZ-bottom < 0x2000) && klabs(nDeltaAngle) < 85)
+ aiPlay3DSound(pSprite, 1400, AI_SFX_PRIORITY_1, -1);
+ break;
+ case 207:
+ if (nDist < 0x1800 && nDist > 0xc00 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ sfxPlay3DSound(pSprite, 1457, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleSBlast);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 206)
+ {
+ sfxPlay3DSound(pSprite, 1457, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleSBlast);
+ }
+ break;
+ default:
+ sfxPlay3DSound(pSprite, 1457, 0, 0);
+ aiNewState(pSprite, pXSprite, &gargoyleSBlast);
+ break;
+ }
+ }
+ else if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 206)
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &gargoyleFSlash);
+ break;
+ }
+ }
+ else if ((height2-height > 0x2000 || floorZ-bottom > 0x2000) && nDist < 0x1400 && nDist > 0x800)
+ {
+ if (pSprite->type == 206)
+ aiPlay3DSound(pSprite, 1400, AI_SFX_PRIORITY_1, -1);
+ else
+ aiPlay3DSound(pSprite, 1450, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &gargoyleSwoop);
+ }
+ else if ((height2-height < 0x2000 || floorZ-bottom < 0x2000) && klabs(nDeltaAngle) < 85)
+ aiPlay3DSound(pSprite, 1450, AI_SFX_PRIORITY_1, -1);
+ break;
+ }
+ }
+ return;
+ }
+ else
+ {
+ aiNewState(pSprite, pXSprite, &gargoyleFly);
+ return;
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &gargoyleFGoto);
+ pXSprite->target = -1;
+}
+
+static void entryFStatue(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ DUDEINFO *pDudeInfo = &dudeInfo[6];
+ actHealDude(pXSprite, pDudeInfo->startHealth, pDudeInfo->startHealth);
+ pSprite->type = 206;
+}
+
+static void entrySStatue(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ DUDEINFO *pDudeInfo = &dudeInfo[7];
+ actHealDude(pXSprite, pDudeInfo->startHealth, pDudeInfo->startHealth);
+ pSprite->type = 207;
+}
+
+static void MoveForward(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if ((unsigned int)Random(64) < 32 && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void MoveSlow(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 = nAccel>>1;
+ t2 >>= 1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ switch (pSprite->type)
+ {
+ case 206:
+ zvel[nSprite] = 0x44444;
+ break;
+ case 207:
+ zvel[nSprite] = 0x35555;
+ break;
+ }
+}
+
+static void MoveSwoop(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ switch (pSprite->type)
+ {
+ case 206:
+ zvel[nSprite] = t1;
+ break;
+ case 207:
+ zvel[nSprite] = t1;
+ break;
+ }
+}
+
+static void MoveFly(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pSprite->ang = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ switch (pSprite->type)
+ {
+ case 206:
+ zvel[nSprite] = -t1;
+ break;
+ case 207:
+ zvel[nSprite] = -t1;
+ break;
+ }
+ klabs(zvel[nSprite]);
+}
diff --git a/source/blood/src/aigarg.h b/source/blood/src/aigarg.h
new file mode 100644
index 000000000..3112cf85e
--- /dev/null
+++ b/source/blood/src/aigarg.h
@@ -0,0 +1,50 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE gargoyleFIdle;
+extern AISTATE gargoyleStatueIdle;
+extern AISTATE gargoyleFChase;
+extern AISTATE gargoyleFGoto;
+extern AISTATE gargoyleFSlash;
+extern AISTATE gargoyleFThrow;
+extern AISTATE gargoyleSThrow;
+extern AISTATE gargoyleSBlast;
+extern AISTATE gargoyleFRecoil;
+extern AISTATE gargoyleFSearch;
+extern AISTATE gargoyleFMorph2;
+extern AISTATE gargoyleFMorph;
+extern AISTATE gargoyleSMorph2;
+extern AISTATE gargoyleSMorph;
+extern AISTATE gargoyleSwoop;
+extern AISTATE gargoyleFly;
+extern AISTATE gargoyleTurn;
+extern AISTATE gargoyleDodgeUp;
+extern AISTATE gargoyleFDodgeUpRight;
+extern AISTATE gargoyleFDodgeUpLeft;
+extern AISTATE gargoyleDodgeDown;
+extern AISTATE gargoyleFDodgeDownRight;
+extern AISTATE gargoyleFDodgeDownLeft;
+extern AISTATE statueFBreakSEQ;
+extern AISTATE statueSBreakSEQ;
diff --git a/source/blood/src/aighost.cpp b/source/blood/src/aighost.cpp
new file mode 100644
index 000000000..502d5768f
--- /dev/null
+++ b/source/blood/src/aighost.cpp
@@ -0,0 +1,587 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aighost.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void SlashSeqCallback(int, int);
+static void ThrowSeqCallback(int, int);
+static void BlastSeqCallback(int, int);
+static void thinkTarget(spritetype *, XSPRITE *);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void MoveDodgeUp(spritetype *, XSPRITE *);
+static void MoveDodgeDown(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void MoveForward(spritetype *, XSPRITE *);
+static void MoveSlow(spritetype *, XSPRITE *);
+static void MoveSwoop(spritetype *, XSPRITE *);
+static void MoveFly(spritetype *, XSPRITE *);
+
+static int nSlashClient = seqRegisterClient(SlashSeqCallback);
+static int nThrowClient = seqRegisterClient(ThrowSeqCallback);
+static int nBlastClient = seqRegisterClient(BlastSeqCallback);
+
+AISTATE ghostIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, thinkTarget, NULL };
+AISTATE ghostChase = { kAiStateChase, 0, -1, 0, NULL, MoveForward, thinkChase, &ghostIdle };
+AISTATE ghostGoto = { kAiStateMove, 0, -1, 600, NULL, MoveForward, thinkGoto, &ghostIdle };
+AISTATE ghostSlash = { kAiStateChase, 6, nSlashClient, 120, NULL, NULL, NULL, &ghostChase };
+AISTATE ghostThrow = { kAiStateChase, 6, nThrowClient, 120, NULL, NULL, NULL, &ghostChase };
+AISTATE ghostBlast = { kAiStateChase, 6, nBlastClient, 120, NULL, MoveSlow, NULL, &ghostChase };
+AISTATE ghostRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &ghostChase };
+AISTATE ghostTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &ghostChase };
+AISTATE ghostSearch = { kAiStateSearch, 0, -1, 120, NULL, MoveForward, thinkSearch, &ghostIdle };
+AISTATE ghostSwoop = { kAiStateOther, 0, -1, 120, NULL, MoveSwoop, thinkChase, &ghostChase };
+AISTATE ghostFly = { kAiStateMove, 0, -1, 0, NULL, MoveFly, thinkChase, &ghostChase };
+AISTATE ghostTurn = { kAiStateMove, 0, -1, 120, NULL, aiMoveTurn, NULL, &ghostChase };
+AISTATE ghostDodgeUp = { kAiStateMove, 0, -1, 60, NULL, MoveDodgeUp, NULL, &ghostChase };
+AISTATE ghostDodgeUpRight = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeUp, NULL, &ghostChase };
+AISTATE ghostDodgeUpLeft = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeUp, NULL, &ghostChase };
+AISTATE ghostDodgeDown = { kAiStateMove, 0, -1, 120, NULL, MoveDodgeDown, NULL, &ghostChase };
+AISTATE ghostDodgeDownRight = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeDown, NULL, &ghostChase };
+AISTATE ghostDodgeDownLeft = { kAiStateMove, 0, -1, 90, NULL, MoveDodgeDown, NULL, &ghostChase };
+
+static void SlashSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type-kDudeBase];
+ int height = (pSprite->yrepeat*pDudeInfo->eyeHeight)<<2;
+ int height2 = (pTarget->yrepeat*pDudeInfoT->eyeHeight)<<2;
+ int dz = height-height2;
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ sfxPlay3DSound(pSprite, 1406, 0, 0);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_12);
+ int r1 = Random(50);
+ int r2 = Random(50);
+ actFireVector(pSprite, 0, 0, dx+r2, dy-r1, dz, VECTOR_TYPE_12);
+ r1 = Random(50);
+ r2 = Random(50);
+ actFireVector(pSprite, 0, 0, dx-r2, dy+r1, dz, VECTOR_TYPE_12);
+}
+
+static void ThrowSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ actFireThing(&sprite[nSprite], 0, 0, gDudeSlope[nXSprite]-7500, 421, 0xeeeee);
+}
+
+static void BlastSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ wrand(); // ???
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int height = (pSprite->yrepeat*dudeInfo[pSprite->type-kDudeBase].eyeHeight) << 2;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nDist) = approxDist(dx, dy);
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = height;
+ TARGETTRACK tt = { 0x10000, 0x10000, 0x100, 0x55, 0x1aaaaa };
+ Aim aim;
+ aim.dx = Cos(pSprite->ang)>>16;
+ aim.dy = Sin(pSprite->ang)>>16;
+ aim.dz = gDudeSlope[nXSprite];
+ int nClosest = 0x7fffffff;
+ for (short nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite == pSprite2 || !(pSprite2->hitag&8))
+ continue;
+ int x2 = pSprite2->x;
+ int y2 = pSprite2->y;
+ int z2 = pSprite2->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 0x2800)
+ continue;
+ if (tt.at10)
+ {
+ int t = divscale(nDist, tt.at10, 12);
+ x2 += (xvel[nSprite2]*t)>>12;
+ y2 += (yvel[nSprite2]*t)>>12;
+ z2 += (zvel[nSprite2]*t)>>8;
+ }
+ int tx = x+mulscale30(Cos(pSprite->ang), nDist);
+ int ty = y+mulscale30(Sin(pSprite->ang), nDist);
+ int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
+ int tsr = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (tz-tsr > bottom || tz+tsr < top)
+ continue;
+ int dx = (tx-x2)>>4;
+ int dy = (ty-y2)>>4;
+ int dz = (tz-z2)>>8;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 < nClosest)
+ {
+ int nAngle = getangle(x2-x, y2-y);
+ int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
+ if (klabs(nDeltaAngle) <= tt.at8)
+ {
+ int tz = pSprite2->z-pSprite->z;
+ if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(nAngle)>>16;
+ aim.dy = Sin(nAngle)>>16;
+ aim.dz = divscale(tz, nDist, 10);
+ if (tz > -0x333)
+ aim.dz = divscale(tz, nDist, 10);
+ else if (tz < -0x333 && tz > -0xb33)
+ aim.dz = divscale(tz, nDist, 10)+9460;
+ else if (tz < -0xb33 && tz > -0x3000)
+ aim.dz = divscale(tz, nDist, 10)+9460;
+ else if (tz < -0x3000)
+ aim.dz = divscale(tz, nDist, 10)-7500;
+ else
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ else
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ }
+ }
+ if (IsPlayerSprite(pTarget) || !VanillaMode()) // By NoOne: allow fire missile in non-player targets if not a demo
+ {
+ sfxPlay3DSound(pSprite, 489, 0, 0);
+ actFireMissile(pSprite, 0, 0, aim.dx, aim.dy, aim.dz, 307);
+ }
+}
+
+static void thinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ if (pDudeExtraE->at8 && pDudeExtraE->at4 < 10)
+ pDudeExtraE->at4++;
+ else if (pDudeExtraE->at4 >= 10 && pDudeExtraE->at8)
+ {
+ pXSprite->goalAng += 256;
+ POINT3D *pTarget = &baseSprite[pSprite->index];
+ aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
+ aiNewState(pSprite, pXSprite, &ghostTurn);
+ return;
+ }
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ pDudeExtraE->at4 = 0;
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ pDudeExtraE->at4 = 0;
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ }
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &ghostSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void MoveDodgeUp(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -0x1d555;
+}
+
+static void MoveDodgeDown(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ if (pXSprite->dodgeDir == 0)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int dx = xvel[nSprite];
+ int dy = yvel[nSprite];
+ int t1 = dmulscale30(dx, nCos, dy, nSin);
+ int t2 = dmulscale30(dx, nSin, -dy, nCos);
+ if (pXSprite->dodgeDir > 0)
+ t2 += pDudeInfo->sideSpeed;
+ else
+ t2 -= pDudeInfo->sideSpeed;
+
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = 0x44444;
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &ghostGoto);
+ return;
+ }
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &ghostSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &ghostSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ // Should be dudeInfo[pTarget->type-kDudeBase]
+ int height2 = (pDudeInfo->eyeHeight*pTarget->yrepeat)<<2;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int floorZ = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ switch (pSprite->type)
+ {
+ case 210:
+ if (nDist < 0x2000 && nDist > 0x1000 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &ghostBlast);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 210)
+ aiNewState(pSprite, pXSprite, &ghostBlast);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &ghostBlast);
+ break;
+ }
+ }
+ else if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &ghostSlash);
+ break;
+ case 0:
+ case 4:
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type && sprite[gHitInfo.hitsprite].type != 210)
+ aiNewState(pSprite, pXSprite, &ghostSlash);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &ghostSlash);
+ break;
+ }
+ }
+ else if ((height2-height > 0x2000 || floorZ-bottom > 0x2000) && nDist < 0x1400 && nDist > 0x800)
+ {
+ aiPlay3DSound(pSprite, 1600, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &ghostSwoop);
+ }
+ else if ((height2-height < 0x2000 || floorZ-bottom < 0x2000) && klabs(nDeltaAngle) < 85)
+ aiPlay3DSound(pSprite, 1600, AI_SFX_PRIORITY_1, -1);
+ break;
+ }
+ }
+ return;
+ }
+ else
+ {
+ aiNewState(pSprite, pXSprite, &ghostFly);
+ return;
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &ghostGoto);
+ pXSprite->target = -1;
+}
+
+static void MoveForward(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if ((unsigned int)Random(64) < 32 && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void MoveSlow(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 = nAccel>>1;
+ t2 >>= 1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ switch (pSprite->type)
+ {
+ case 210:
+ zvel[nSprite] = 0x44444;
+ break;
+ }
+}
+
+static void MoveSwoop(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ switch (pSprite->type)
+ {
+ case 210:
+ zvel[nSprite] = t1;
+ break;
+ }
+}
+
+static void MoveFly(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = pDudeInfo->frontSpeed<<2;
+ if (klabs(nAng) > 341)
+ {
+ pSprite->ang = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ switch (pSprite->type)
+ {
+ case 210:
+ zvel[nSprite] = -t1;
+ break;
+ }
+}
diff --git a/source/blood/src/aighost.h b/source/blood/src/aighost.h
new file mode 100644
index 000000000..b45504f9b
--- /dev/null
+++ b/source/blood/src/aighost.h
@@ -0,0 +1,42 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+extern AISTATE ghostIdle;
+extern AISTATE ghostChase;
+extern AISTATE ghostGoto;
+extern AISTATE ghostSlash;
+extern AISTATE ghostThrow;
+extern AISTATE ghostBlast;
+extern AISTATE ghostRecoil;
+extern AISTATE ghostTeslaRecoil;
+extern AISTATE ghostSearch;
+extern AISTATE ghostSwoop;
+extern AISTATE ghostFly;
+extern AISTATE ghostTurn;
+extern AISTATE ghostDodgeUp;
+extern AISTATE ghostDodgeUpRight;
+extern AISTATE ghostDodgeUpLeft;
+extern AISTATE ghostDodgeDown;
+extern AISTATE ghostDodgeDownRight;
+extern AISTATE ghostDodgeDownLeft;
diff --git a/source/blood/src/aigilbst.cpp b/source/blood/src/aigilbst.cpp
new file mode 100644
index 000000000..ea31fccad
--- /dev/null
+++ b/source/blood/src/aigilbst.cpp
@@ -0,0 +1,415 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aigilbst.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void GillBiteSeqCallback(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void thinkSwimGoto(spritetype *, XSPRITE *);
+static void thinkSwimChase(spritetype *, XSPRITE *);
+static void sub_6CB00(spritetype *, XSPRITE *);
+static void sub_6CD74(spritetype *, XSPRITE *);
+static void sub_6D03C(spritetype *, XSPRITE *);
+
+static int nGillBiteClient = seqRegisterClient(GillBiteSeqCallback);
+
+AISTATE gillBeastIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE gillBeastChase = { kAiStateChase, 9, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE gillBeastDodge = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &gillBeastChase };
+AISTATE gillBeastGoto = { kAiStateMove, 9, -1, 600, NULL, aiMoveForward, thinkGoto, &gillBeastIdle };
+AISTATE gillBeastBite = { kAiStateChase, 6, nGillBiteClient, 120, NULL, NULL, NULL, &gillBeastChase };
+AISTATE gillBeastSearch = { kAiStateMove, 9, -1, 120, NULL, aiMoveForward, thinkSearch, &gillBeastIdle };
+AISTATE gillBeastRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &gillBeastDodge };
+AISTATE gillBeastSwimIdle = { kAiStateIdle, 10, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE gillBeastSwimChase = { kAiStateChase, 10, -1, 0, NULL, sub_6CB00, thinkSwimChase, NULL };
+AISTATE gillBeastSwimDodge = { kAiStateMove, 10, -1, 90, NULL, aiMoveDodge, NULL, &gillBeastSwimChase };
+AISTATE gillBeastSwimGoto = { kAiStateMove, 10, -1, 600, NULL, aiMoveForward, thinkSwimGoto, &gillBeastSwimIdle };
+AISTATE gillBeastSwimSearch = { kAiStateSearch, 10, -1, 120, NULL, aiMoveForward, thinkSearch, &gillBeastSwimIdle };
+AISTATE gillBeastSwimBite = { kAiStateChase, 7, nGillBiteClient, 0, NULL, NULL, thinkSwimChase, &gillBeastSwimChase };
+AISTATE gillBeastSwimRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &gillBeastSwimDodge };
+AISTATE gillBeast13A138 = { kAiStateOther, 10, -1, 120, NULL, sub_6CD74, thinkSwimChase, &gillBeastSwimChase };
+AISTATE gillBeast13A154 = { kAiStateOther, 10, -1, 0, NULL, sub_6D03C, thinkSwimChase, &gillBeastSwimChase };
+AISTATE gillBeast13A170 = { kAiStateOther, 10, -1, 120, NULL, NULL, aiMoveTurn, &gillBeastSwimChase };
+
+static void GillBiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ int dz = pSprite->z-pTarget->z;
+ dx += Random3(2000);
+ dy += Random3(2000);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_8);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_8);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_8);
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastSearch);
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastSearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int nXSprite = sprite[pXSprite->reference].extra;
+ gDudeSlope[nXSprite] = divscale(pTarget->z-pSprite->z, nDist, 10);
+ if (nDist < 921 && klabs(nDeltaAngle) < 28)
+ {
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimBite);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastBite);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimBite);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastBite);
+ }
+ else
+ {
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimDodge);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastDodge);
+ }
+ break;
+ default:
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimBite);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastBite);
+ break;
+ }
+ }
+ }
+ return;
+ }
+ }
+
+ XSECTOR *pXSector;
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0)
+ pXSector = &xsector[nXSector];
+ else
+ pXSector = NULL;
+ if (pXSector && pXSector->Underwater)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimGoto);
+ else
+ aiNewState(pSprite, pXSprite, &gillBeastGoto);
+ sfxPlay3DSound(pSprite, 1701, -1, 0);
+ pXSprite->target = -1;
+}
+
+static void thinkSwimGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkSwimChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &gillBeastSwimSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = pDudeInfo->eyeHeight+pSprite->z;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ int UNUSED(floorZ) = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &gillBeastSwimBite);
+ else
+ {
+ aiPlay3DSound(pSprite, 1700, AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &gillBeast13A154);
+ }
+ }
+ }
+ else
+ aiNewState(pSprite, pXSprite, &gillBeast13A154);
+ return;
+ }
+ aiNewState(pSprite, pXSprite, &gillBeastSwimGoto);
+ pXSprite->target = -1;
+}
+
+static void sub_6CB00(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = (pDudeInfo->frontSpeed-(((4-gGameOptions.nDifficulty)<<27)/120)/120)<<2;
+ if (klabs(nAng) > 341)
+ return;
+ if (pXSprite->target == -1)
+ pSprite->ang = (pSprite->ang+256)&2047;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Random(64) < 32 && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ if (pXSprite->target == -1)
+ t1 += nAccel;
+ else
+ t1 += nAccel>>2;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+}
+
+static void sub_6CD74(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int z = pSprite->z + dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+ int z2 = pTarget->z + dudeInfo[pTarget->type - kDudeBase].eyeHeight;
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = (pDudeInfo->frontSpeed-(((4-gGameOptions.nDifficulty)<<27)/120)/120)<<2;
+ if (klabs(nAng) > 341)
+ {
+ pXSprite->goalAng = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int dz = z2 - z;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x600) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = -dz;
+}
+
+static void sub_6D03C(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ int nSprite = pSprite->index;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ int z = pSprite->z + dudeInfo[pSprite->type - kDudeBase].eyeHeight;
+ int z2 = pTarget->z + dudeInfo[pTarget->type - kDudeBase].eyeHeight;
+ int nAng = ((pXSprite->goalAng+1024-pSprite->ang)&2047)-1024;
+ int nTurnRange = (pDudeInfo->angSpeed<<2)>>4;
+ pSprite->ang = (pSprite->ang+ClipRange(nAng, -nTurnRange, nTurnRange))&2047;
+ int nAccel = (pDudeInfo->frontSpeed-(((4-gGameOptions.nDifficulty)<<27)/120)/120)<<2;
+ if (klabs(nAng) > 341)
+ {
+ pSprite->ang = (pSprite->ang+512)&2047;
+ return;
+ }
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int dz = (z2 - z)<<3;
+ int UNUSED(nAngle) = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ if (Chance(0x4000) && nDist <= 0x400)
+ return;
+ int nCos = Cos(pSprite->ang);
+ int nSin = Sin(pSprite->ang);
+ int vx = xvel[nSprite];
+ int vy = yvel[nSprite];
+ int t1 = dmulscale30(vx, nCos, vy, nSin);
+ int t2 = dmulscale30(vx, nSin, -vy, nCos);
+ t1 += nAccel>>1;
+ xvel[nSprite] = dmulscale30(t1, nCos, t2, nSin);
+ yvel[nSprite] = dmulscale30(t1, nSin, -t2, nCos);
+ zvel[nSprite] = dz;
+}
diff --git a/source/blood/src/aigilbst.h b/source/blood/src/aigilbst.h
new file mode 100644
index 000000000..e20610e04
--- /dev/null
+++ b/source/blood/src/aigilbst.h
@@ -0,0 +1,43 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE gillBeastIdle;
+extern AISTATE gillBeastChase;
+extern AISTATE gillBeastDodge;
+extern AISTATE gillBeastGoto;
+extern AISTATE gillBeastBite;
+extern AISTATE gillBeastSearch;
+extern AISTATE gillBeastRecoil;
+extern AISTATE gillBeastSwimIdle;
+extern AISTATE gillBeastSwimChase;
+extern AISTATE gillBeastSwimDodge;
+extern AISTATE gillBeastSwimGoto;
+extern AISTATE gillBeastSwimSearch;
+extern AISTATE gillBeastSwimBite;
+extern AISTATE gillBeastSwimRecoil;
+extern AISTATE gillBeast13A138;
+extern AISTATE gillBeast13A154;
+extern AISTATE gillBeast13A170;
+
diff --git a/source/blood/src/aihand.cpp b/source/blood/src/aihand.cpp
new file mode 100644
index 000000000..f611f12c5
--- /dev/null
+++ b/source/blood/src/aihand.cpp
@@ -0,0 +1,138 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aihand.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void HandJumpSeqCallback(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+
+static int nJumpClient = seqRegisterClient(HandJumpSeqCallback);
+
+AISTATE handIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE hand13A3B4 = { kAiStateOther, 0, -1, 0, NULL, NULL, NULL, NULL };
+AISTATE handSearch = { kAiStateMove, 6, -1, 600, NULL, aiMoveForward, thinkSearch, &handIdle };
+AISTATE handChase = { kAiStateChase, 6, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE handRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &handSearch };
+AISTATE handGoto = { kAiStateMove, 6, -1, 1800, NULL, aiMoveForward, thinkGoto, &handIdle };
+AISTATE handJump = { kAiStateChase, 7, nJumpClient, 120, NULL, NULL, NULL, &handChase };
+
+static void HandJumpSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ if (IsPlayerSprite(pTarget))
+ {
+ PLAYER *pPlayer = &gPlayer[pTarget->type-kDudePlayer1];
+ if (!pPlayer->at376)
+ {
+ pPlayer->at376 = 1;
+ actPostSprite(pSprite->index, kStatFree);
+ }
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &handSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &handGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &handSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &handSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x233 && klabs(nDeltaAngle) < 85 && gGameOptions.nGameType == 0)
+ aiNewState(pSprite, pXSprite, &handJump);
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &handGoto);
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aihand.h b/source/blood/src/aihand.h
new file mode 100644
index 000000000..8e4b03ea7
--- /dev/null
+++ b/source/blood/src/aihand.h
@@ -0,0 +1,32 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE handIdle;
+extern AISTATE hand13A3B4;
+extern AISTATE handSearch;
+extern AISTATE handChase;
+extern AISTATE handRecoil;
+extern AISTATE handGoto;
+extern AISTATE handJump;
\ No newline at end of file
diff --git a/source/blood/src/aihound.cpp b/source/blood/src/aihound.cpp
new file mode 100644
index 000000000..ab60ae2e8
--- /dev/null
+++ b/source/blood/src/aihound.cpp
@@ -0,0 +1,160 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aihound.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void BiteSeqCallback(int, int);
+static void BurnSeqCallback(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+
+static int nBiteClient = seqRegisterClient(BiteSeqCallback);
+static int nBurnClient = seqRegisterClient(BurnSeqCallback);
+
+AISTATE houndIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE houndSearch = { kAiStateMove, 8, -1, 1800, NULL, aiMoveForward, thinkSearch, &houndIdle };
+AISTATE houndChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE houndRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &houndSearch };
+AISTATE houndTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &houndSearch };
+AISTATE houndGoto = { kAiStateMove, 8, -1, 600, NULL, aiMoveForward, thinkGoto, &houndIdle };
+AISTATE houndBite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &houndChase };
+AISTATE houndBurn = { kAiStateChase, 7, nBurnClient, 60, NULL, NULL, NULL, &houndChase };
+
+static void BiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ if (IsPlayerSprite(pTarget) || !VanillaMode()) // allow to hit non-player targets if not a demo
+ actFireVector(pSprite, 0, 0, dx, dy, pTarget->z-pSprite->z, VECTOR_TYPE_15);
+}
+
+static void BurnSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ actFireMissile(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, 308);
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &houndGoto);
+ return;
+ }
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &houndSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0xb00 && nDist > 0x500 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &houndBurn);
+ else if(nDist < 0x266 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &houndBite);
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &houndGoto);
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aihound.h b/source/blood/src/aihound.h
new file mode 100644
index 000000000..156a82cd0
--- /dev/null
+++ b/source/blood/src/aihound.h
@@ -0,0 +1,33 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE houndIdle;
+extern AISTATE houndSearch;
+extern AISTATE houndChase;
+extern AISTATE houndRecoil;
+extern AISTATE houndTeslaRecoil;
+extern AISTATE houndGoto;
+extern AISTATE houndBite;
+extern AISTATE houndBurn;
diff --git a/source/blood/src/aiinnoc.cpp b/source/blood/src/aiinnoc.cpp
new file mode 100644
index 000000000..360551b97
--- /dev/null
+++ b/source/blood/src/aiinnoc.cpp
@@ -0,0 +1,119 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aiinnoc.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+
+AISTATE innocentIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE innocentSearch = { kAiStateSearch, 6, -1, 1800, NULL, aiMoveForward, thinkSearch, &innocentIdle };
+AISTATE innocentChase = { kAiStateChase, 6, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE innocentRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &innocentChase };
+AISTATE innocentTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &innocentChase };
+AISTATE innocentGoto = { kAiStateMove, 6, -1, 600, NULL, aiMoveForward, thinkGoto, &innocentIdle };
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &innocentSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &innocentGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &innocentSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget))
+ {
+ aiNewState(pSprite, pXSprite, &innocentSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x666 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &innocentIdle);
+ return;
+ }
+ }
+ }
+
+ aiPlay3DSound(pSprite, 7000+Random(6), AI_SFX_PRIORITY_1, -1);
+ aiNewState(pSprite, pXSprite, &innocentGoto);
+ pXSprite->target = -1;
+}
+
diff --git a/source/blood/src/aiinnoc.h b/source/blood/src/aiinnoc.h
new file mode 100644
index 000000000..e121b220c
--- /dev/null
+++ b/source/blood/src/aiinnoc.h
@@ -0,0 +1,31 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE innocentIdle;
+extern AISTATE innocentSearch;
+extern AISTATE innocentChase;
+extern AISTATE innocentRecoil;
+extern AISTATE innocentTeslaRecoil;
+extern AISTATE innocentGoto;
\ No newline at end of file
diff --git a/source/blood/src/aipod.cpp b/source/blood/src/aipod.cpp
new file mode 100644
index 000000000..ac69d947d
--- /dev/null
+++ b/source/blood/src/aipod.cpp
@@ -0,0 +1,282 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aipod.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void sub_6FF08(int, int);
+static void sub_6FF54(int, int);
+static void sub_6FFA0(int, int);
+static void sub_70284(int, int);
+static void sub_7034C(spritetype *, XSPRITE *);
+static void sub_70380(spritetype *, XSPRITE *);
+static void sub_704D8(spritetype *, XSPRITE *);
+
+static int dword_279B34 = seqRegisterClient(sub_6FFA0);
+static int dword_279B38 = seqRegisterClient(sub_70284);
+static int dword_279B3C = seqRegisterClient(sub_6FF08);
+static int dword_279B40 = seqRegisterClient(sub_6FF54);
+
+AISTATE podIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE pod13A600 = { kAiStateMove, 7, -1, 3600, NULL, aiMoveTurn, sub_70380, &podSearch };
+AISTATE podSearch = { kAiStateSearch, 0, -1, 3600, NULL, aiMoveTurn, sub_7034C, &podSearch };
+AISTATE pod13A638 = { kAiStateChase, 8, dword_279B34, 600, NULL, NULL, NULL, &podChase };
+AISTATE podRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &podChase };
+AISTATE podChase = { kAiStateChase, 6, -1, 0, NULL, aiMoveTurn, sub_704D8, NULL };
+AISTATE tentacleIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE tentacle13A6A8 = { kAiStateOther, 7, dword_279B3C, 0, NULL, NULL, NULL, &tentacle13A6C4 };
+AISTATE tentacle13A6C4 = { kAiStateOther, -1, -1, 0, NULL, NULL, NULL, &tentacleChase };
+AISTATE tentacle13A6E0 = { kAiStateOther, 8, dword_279B40, 0, NULL, NULL, NULL, &tentacle13A6FC };
+AISTATE tentacle13A6FC = { kAiStateOther, -1, -1, 0, NULL, NULL, NULL, &tentacleIdle };
+AISTATE tentacle13A718 = { kAiStateOther, 8, -1, 3600, NULL, aiMoveTurn, sub_70380, &tentacleSearch };
+AISTATE tentacleSearch = { kAiStateOther, 0, -1, 3600, NULL, aiMoveTurn, sub_7034C, NULL };
+AISTATE tentacle13A750 = { kAiStateOther, 6, dword_279B38, 120, NULL, NULL, NULL, &tentacleChase };
+AISTATE tentacleRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &tentacleChase };
+AISTATE tentacleChase = { kAiStateChase, 6, -1, 0, NULL, aiMoveTurn, sub_704D8, NULL };
+
+static void sub_6FF08(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ sfxPlay3DSound(&sprite[nSprite], 2503, -1, 0);
+}
+
+static void sub_6FF54(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ sfxPlay3DSound(&sprite[nSprite], 2500, -1, 0);
+}
+
+static void sub_6FFA0(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int x = pTarget->x-pSprite->x;
+ int y = pTarget->y-pSprite->y;
+ int dz = pTarget->z-pSprite->z;
+ x += Random2(1000);
+ y += Random2(1000);
+ int nDist = approxDist(x, y);
+ int nDist2 = nDist / 540;
+ spritetype *pMissile = NULL;
+ switch (pSprite->type)
+ {
+ case 221:
+ dz += 8000;
+ if (pDudeInfo->seeDist*0.1 < nDist)
+ {
+ if (Chance(0x8000))
+ sfxPlay3DSound(pSprite, 2474, -1, 0);
+ else
+ sfxPlay3DSound(pSprite, 2475, -1, 0);
+ pMissile = actFireThing(pSprite, 0, -8000, dz/128-14500, 430, (nDist2<<23)/120);
+ }
+ if (pMissile)
+ seqSpawn(68, 3, pMissile->extra, -1);
+ break;
+ case 223:
+ dz += 8000;
+ if (pDudeInfo->seeDist*0.1 < nDist)
+ {
+ sfxPlay3DSound(pSprite, 2454, -1, 0);
+ pMissile = actFireThing(pSprite, 0, -8000, dz/128-14500, 429, (nDist2<<23)/120);
+ }
+ if (pMissile)
+ seqSpawn(22, 3, pMissile->extra, -1);
+ break;
+ }
+ for (int i = 0; i < 4; i++)
+ sub_746D4(pSprite, 240);
+}
+
+static void sub_70284(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ sfxPlay3DSound(pSprite, 2502, -1, 0);
+ int nDist, nBurn;
+ DAMAGE_TYPE dmgType;
+ switch (pSprite->type)
+ {
+ case 222:
+ default:
+ nBurn = 0;
+ dmgType = DAMAGE_TYPE_2;
+ nDist = 50;
+ break;
+ case 224:
+ nBurn = (gGameOptions.nDifficulty*120)>>2;
+ dmgType = DAMAGE_TYPE_3;
+ nDist = 75;
+ break;
+ }
+ sub_2A620(nSprite, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, nDist, 1, 5*(1+gGameOptions.nDifficulty), dmgType, 2, nBurn, 0, 0);
+}
+
+static void sub_7034C(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void sub_70380(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ {
+ switch (pSprite->type)
+ {
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &podSearch);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacleSearch);
+ break;
+ }
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void sub_704D8(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ switch (pSprite->type)
+ {
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &pod13A600);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacle13A718);
+ break;
+ }
+ return;
+ }
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ switch (pSprite->type)
+ {
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &podSearch);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacleSearch);
+ break;
+ }
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (klabs(nDeltaAngle) < 85 && pTarget->type != 221 && pTarget->type != 223)
+ {
+ switch (pSprite->type)
+ {
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &pod13A638);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacle13A750);
+ break;
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ switch (pSprite->type)
+ {
+ case 221:
+ case 223:
+ aiNewState(pSprite, pXSprite, &pod13A600);
+ break;
+ case 222:
+ case 224:
+ aiNewState(pSprite, pXSprite, &tentacle13A718);
+ break;
+ }
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aipod.h b/source/blood/src/aipod.h
new file mode 100644
index 000000000..5ef41fa66
--- /dev/null
+++ b/source/blood/src/aipod.h
@@ -0,0 +1,41 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE podIdle;
+extern AISTATE pod13A600;
+extern AISTATE podSearch;
+extern AISTATE pod13A638;
+extern AISTATE podRecoil;
+extern AISTATE podChase;
+extern AISTATE tentacleIdle;
+extern AISTATE tentacle13A6A8;
+extern AISTATE tentacle13A6C4;
+extern AISTATE tentacle13A6E0;
+extern AISTATE tentacle13A6FC;
+extern AISTATE tentacle13A718;
+extern AISTATE tentacleSearch;
+extern AISTATE tentacle13A750;
+extern AISTATE tentacleRecoil;
+extern AISTATE tentacleChase;
diff --git a/source/blood/src/airat.cpp b/source/blood/src/airat.cpp
new file mode 100644
index 000000000..ac3884044
--- /dev/null
+++ b/source/blood/src/airat.cpp
@@ -0,0 +1,135 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "airat.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void BiteSeqCallback(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+
+static int nBiteClient = seqRegisterClient(BiteSeqCallback);
+
+AISTATE ratIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE ratSearch = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &ratIdle };
+AISTATE ratChase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE ratDodge = { kAiStateMove, 7, -1, 0, NULL, NULL, NULL, &ratChase };
+AISTATE ratRecoil = { kAiStateRecoil, 7, -1, 0, NULL, NULL, NULL, &ratDodge };
+AISTATE ratGoto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &ratIdle };
+AISTATE ratBite = { kAiStateChase, 6, nBiteClient, 120, NULL, NULL, NULL, &ratChase };
+
+static void BiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ if (IsPlayerSprite(pTarget))
+ actFireVector(pSprite, 0, 0, dx, dy, pTarget->z-pSprite->z, VECTOR_TYPE_16);
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &ratGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &ratSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &ratBite);
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &ratGoto);
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/airat.h b/source/blood/src/airat.h
new file mode 100644
index 000000000..6c469c7a2
--- /dev/null
+++ b/source/blood/src/airat.h
@@ -0,0 +1,32 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE ratIdle;
+extern AISTATE ratSearch;
+extern AISTATE ratChase;
+extern AISTATE ratDodge;
+extern AISTATE ratRecoil;
+extern AISTATE ratGoto;
+extern AISTATE ratBit;
\ No newline at end of file
diff --git a/source/blood/src/aispid.cpp b/source/blood/src/aispid.cpp
new file mode 100644
index 000000000..5b619b213
--- /dev/null
+++ b/source/blood/src/aispid.cpp
@@ -0,0 +1,290 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aispid.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "endgame.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void SpidBiteSeqCallback(int, int);
+static void SpidJumpSeqCallback(int, int);
+static void sub_71370(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+
+static int nBiteClient = seqRegisterClient(SpidBiteSeqCallback);
+static int nJumpClient = seqRegisterClient(SpidJumpSeqCallback);
+static int dword_279B50 = seqRegisterClient(sub_71370);
+
+AISTATE spidIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE spidChase = { kAiStateChase, 7, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE spidDodge = { kAiStateMove, 7, -1, 90, NULL, aiMoveDodge, NULL, &spidChase };
+AISTATE spidGoto = { kAiStateMove, 7, -1, 600, NULL, aiMoveForward, thinkGoto, &spidIdle };
+AISTATE spidSearch = { kAiStateSearch, 7, -1, 1800, NULL, aiMoveForward, thinkSearch, &spidIdle };
+AISTATE spidBite = { kAiStateChase, 6, nBiteClient, 60, NULL, NULL, NULL, &spidChase };
+AISTATE spidJump = { kAiStateChase, 8, nJumpClient, 60, NULL, aiMoveForward, NULL, &spidChase };
+AISTATE spid13A92C = { kAiStateOther, 0, dword_279B50, 60, NULL, NULL, NULL, &spidIdle };
+
+static char sub_70D30(XSPRITE *pXDude, int a2, int a3)
+{
+ dassert(pXDude != NULL);
+ int nDude = pXDude->reference;
+ spritetype *pDude = &sprite[nDude];
+ if (IsPlayerSprite(pDude))
+ {
+ a2 <<= 4;
+ a3 <<= 4;
+ if (IsPlayerSprite(pDude))
+ {
+ PLAYER *pPlayer = &gPlayer[pDude->type-kDudePlayer1];
+ if (a3 > pPlayer->at36a)
+ {
+ pPlayer->at36a = ClipHigh(pPlayer->at36a+a2, a3);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void SpidBiteSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ dx += Random2(2000);
+ dy += Random2(2000);
+ int dz = Random2(2000);
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ if (IsPlayerSprite(pTarget))
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ if (hit == 3)
+ {
+ if (sprite[gHitInfo.hitsprite].type <= kDudePlayer8 && sprite[gHitInfo.hitsprite].type >= kDudePlayer1)
+ {
+ dz += pTarget->z-pSprite->z;
+ if (pTarget->type >= kDudePlayer1 && pTarget->type <= kDudePlayer8)
+ {
+ PLAYER *pPlayer = &gPlayer[pTarget->type-kDudePlayer1];
+ switch (pSprite->type)
+ {
+ case 213:
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_17);
+ if (IsPlayerSprite(pTarget) && !pPlayer->at31a && powerupCheck(pPlayer, 14) <= 0
+ && Chance(0x4000))
+ powerupActivate(pPlayer, 28);
+ break;
+ case 214:
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_17);
+ if (Chance(0x5000))
+ sub_70D30(pXTarget, 4, 16);
+ break;
+ case 215:
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_17);
+ sub_70D30(pXTarget, 8, 16);
+ break;
+ case 216:
+ {
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_17);
+ dx += Random2(2000);
+ dy += Random2(2000);
+ dz += Random2(2000);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_17);
+ sub_70D30(pXTarget, 8, 16);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void SpidJumpSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ dx += Random2(200);
+ dy += Random2(200);
+ int dz = Random2(200);
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ if (IsPlayerSprite(pTarget))
+ {
+ dz += pTarget->z-pSprite->z;
+ if (pTarget->type >= kDudePlayer1 && pTarget->type <= kDudePlayer8)
+ {
+ switch (pSprite->type)
+ {
+ case 213:
+ case 214:
+ case 215:
+ xvel[nSprite] = dx << 16;
+ yvel[nSprite] = dy << 16;
+ zvel[nSprite] = dz << 16;
+ break;
+ }
+ }
+ }
+}
+
+static void sub_71370(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ DUDEEXTRA_at6_u1 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u1;
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ spritetype *pSpawn = NULL;
+ if (IsPlayerSprite(pTarget) && pDudeExtraE->at4 < 10)
+ {
+ if (nDist < 0x1a00 && nDist > 0x1400 && klabs(pSprite->ang-nAngle) < pDudeInfo->periphery)
+ pSpawn = actSpawnDude(pSprite, 214, pSprite->clipdist, 0);
+ else if (nDist < 0x1400 && nDist > 0xc00 && klabs(pSprite->ang-nAngle) < pDudeInfo->periphery)
+ pSpawn = actSpawnDude(pSprite, 213, pSprite->clipdist, 0);
+ else if (nDist < 0xc00 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ pSpawn = actSpawnDude(pSprite, 213, pSprite->clipdist, 0);
+ if (pSpawn)
+ {
+ pDudeExtraE->at4++;
+ pSpawn->owner = nSprite;
+ gKillMgr.sub_263E0(1);
+ }
+ }
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &spidGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &spidSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ switch (pSprite->type)
+ {
+ case 214:
+ if (nDist < 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &spidBite);
+ break;
+ case 213:
+ case 215:
+ if (nDist < 0x733 && nDist > 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &spidJump);
+ else if (nDist < 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &spidBite);
+ break;
+ case 216:
+ if (nDist < 0x733 && nDist > 0x399 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &spidJump);
+ else if (Chance(0x8000))
+ aiNewState(pSprite, pXSprite, &spid13A92C);
+ break;
+ }
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &spidGoto);
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aispid.h b/source/blood/src/aispid.h
new file mode 100644
index 000000000..152d15199
--- /dev/null
+++ b/source/blood/src/aispid.h
@@ -0,0 +1,33 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE spidIdle;
+extern AISTATE spidChase;
+extern AISTATE spidDodge;
+extern AISTATE spidGoto;
+extern AISTATE spidSearch;
+extern AISTATE spidBite;
+extern AISTATE spidJump;
+extern AISTATE spid13A92C;
diff --git a/source/blood/src/aitchern.cpp b/source/blood/src/aitchern.cpp
new file mode 100644
index 000000000..715e40a0b
--- /dev/null
+++ b/source/blood/src/aitchern.cpp
@@ -0,0 +1,357 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aitchern.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void sub_71A90(int, int);
+static void sub_71BD4(int, int);
+static void sub_720AC(int, int);
+static void sub_72580(spritetype *, XSPRITE *);
+static void sub_725A4(spritetype *, XSPRITE *);
+static void sub_72850(spritetype *, XSPRITE *);
+static void sub_72934(spritetype *, XSPRITE *);
+
+static int dword_279B54 = seqRegisterClient(sub_71BD4);
+static int dword_279B58 = seqRegisterClient(sub_720AC);
+static int dword_279B5C = seqRegisterClient(sub_71A90);
+
+AISTATE tchernobogIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, sub_725A4, NULL };
+AISTATE tchernobogSearch = { kAiStateSearch, 8, -1, 1800, NULL, aiMoveForward, sub_72580, &tchernobogIdle };
+AISTATE tchernobogChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, sub_72934, NULL };
+AISTATE tchernobogRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &tchernobogSearch };
+AISTATE tcherno13A9B8 = { kAiStateMove, 8, -1, 600, NULL, aiMoveForward, sub_72850, &tchernobogIdle };
+AISTATE tcherno13A9D4 = { kAiStateMove, 6, dword_279B54, 60, NULL, NULL, NULL, &tchernobogChase };
+AISTATE tcherno13A9F0 = { kAiStateChase, 6, dword_279B58, 60, NULL, NULL, NULL, &tchernobogChase };
+AISTATE tcherno13AA0C = { kAiStateChase, 7, dword_279B5C, 60, NULL, NULL, NULL, &tchernobogChase };
+AISTATE tcherno13AA28 = { kAiStateChase, 8, -1, 60, NULL, aiMoveTurn, NULL, &tchernobogChase };
+
+static void sub_71A90(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int nTarget = pTarget->index;
+ int nOwner = actSpriteIdToOwnerId(nSprite);
+ if (pXTarget->burnTime == 0)
+ evPost(nTarget, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(nOwner, pXTarget, 40);
+ if (Chance(0x6000))
+ aiNewState(pSprite, pXSprite, &tcherno13A9D4);
+}
+
+static void sub_71BD4(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int height = pSprite->yrepeat*pDudeInfo->eyeHeight;
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = height;
+ TARGETTRACK tt = { 0x10000, 0x10000, 0x100, 0x55, 0x100000 };
+ Aim aim;
+ aim.dx = Cos(pSprite->ang)>>16;
+ aim.dy = Sin(pSprite->ang)>>16;
+ aim.dz = gDudeSlope[nXSprite];
+ int nClosest = 0x7fffffff;
+ for (short nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite == pSprite2 || !(pSprite2->hitag&8))
+ continue;
+ int x2 = pSprite2->x;
+ int y2 = pSprite2->y;
+ int z2 = pSprite2->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 0x2800)
+ continue;
+ if (tt.at10)
+ {
+ int t = divscale(nDist, tt.at10, 12);
+ x2 += (xvel[nSprite2]*t)>>12;
+ y2 += (yvel[nSprite2]*t)>>12;
+ z2 += (zvel[nSprite2]*t)>>8;
+ }
+ int tx = x+mulscale30(Cos(pSprite->ang), nDist);
+ int ty = y+mulscale30(Sin(pSprite->ang), nDist);
+ int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
+ int tsr = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (tz-tsr > bottom || tz+tsr < top)
+ continue;
+ int dx = (tx-x2)>>4;
+ int dy = (ty-y2)>>4;
+ int dz = (tz-z2)>>8;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 < nClosest)
+ {
+ int nAngle = getangle(x2-x, y2-y);
+ int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
+ if (klabs(nDeltaAngle) <= tt.at8)
+ {
+ int tz = pSprite2->z-pSprite->z;
+ if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(nAngle)>>16;
+ aim.dy = Sin(nAngle)>>16;
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ else
+ aim.dz = tz;
+ }
+ }
+ }
+ actFireMissile(pSprite, -350, 0, aim.dx, aim.dy, aim.dz, 314);
+ actFireMissile(pSprite, 350, 0, aim.dx, aim.dy, aim.dz, 314);
+}
+
+static void sub_720AC(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int height = pSprite->yrepeat*pDudeInfo->eyeHeight;
+ int ax, ay, az;
+ ax = Cos(pSprite->ang)>>16;
+ ay = Sin(pSprite->ang)>>16;
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = height;
+ TARGETTRACK tt = { 0x10000, 0x10000, 0x100, 0x55, 0x100000 };
+ Aim aim;
+ aim.dx = ax;
+ aim.dy = ay;
+ aim.dz = gDudeSlope[nXSprite];
+ int nClosest = 0x7fffffff;
+ az = 0;
+ for (short nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nextspritestat[nSprite2])
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ if (pSprite == pSprite2 || !(pSprite2->hitag&8))
+ continue;
+ int x2 = pSprite2->x;
+ int y2 = pSprite2->y;
+ int z2 = pSprite2->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 0x2800)
+ continue;
+ if (tt.at10)
+ {
+ int t = divscale(nDist, tt.at10, 12);
+ x2 += (xvel[nSprite2]*t)>>12;
+ y2 += (yvel[nSprite2]*t)>>12;
+ z2 += (zvel[nSprite2]*t)>>8;
+ }
+ int tx = x+mulscale30(Cos(pSprite->ang), nDist);
+ int ty = y+mulscale30(Sin(pSprite->ang), nDist);
+ int tz = z+mulscale(gDudeSlope[nXSprite], nDist, 10);
+ int tsr = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite2, &top, &bottom);
+ if (tz-tsr > bottom || tz+tsr < top)
+ continue;
+ int dx = (tx-x2)>>4;
+ int dy = (ty-y2)>>4;
+ int dz = (tz-z2)>>8;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 < nClosest)
+ {
+ int nAngle = getangle(x2-x, y2-y);
+ int nDeltaAngle = ((nAngle-pSprite->ang+1024)&2047)-1024;
+ if (klabs(nDeltaAngle) <= tt.at8)
+ {
+ int tz = pSprite2->z-pSprite->z;
+ if (cansee(x, y, z, pSprite->sectnum, x2, y2, z2, pSprite2->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(nAngle)>>16;
+ aim.dy = Sin(nAngle)>>16;
+ aim.dz = divscale(tz, nDist, 10);
+ }
+ else
+ aim.dz = tz;
+ }
+ }
+ }
+ actFireMissile(pSprite, 350, 0, aim.dx, aim.dy, -aim.dz, 314);
+ actFireMissile(pSprite, -350, 0, ax, ay, az, 314);
+}
+
+static void sub_72580(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void sub_725A4(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEEXTRA_at6_u2 *pDudeExtraE = &gDudeExtra[pSprite->extra].at6.u2;
+ if (pDudeExtraE->at4 && pDudeExtraE->at0 < 10)
+ pDudeExtraE->at0++;
+ else if (pDudeExtraE->at0 >= 10 && pDudeExtraE->at4)
+ {
+ pXSprite->goalAng += 256;
+ POINT3D *pTarget = &baseSprite[pSprite->index];
+ aiSetTarget(pXSprite, pTarget->x, pTarget->y, pTarget->z);
+ aiNewState(pSprite, pXSprite, &tcherno13AA28);
+ return;
+ }
+ if (Chance(pDudeInfo->alertChance))
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ if (pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ pDudeExtraE->at0 = 0;
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ pDudeExtraE->at0 = 0;
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else
+ continue;
+ break;
+ }
+ }
+}
+
+static void sub_72850(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &tchernobogSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void sub_72934(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &tcherno13A9B8);
+ return;
+ }
+ if (!(pSprite->type >= kDudeBase && pSprite->type < kDudeMax))
+ return;
+ //dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+ //dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &tchernobogSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0)
+ {
+ aiNewState(pSprite, pXSprite, &tchernobogSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x1f00 && nDist > 0xd00 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &tcherno13AA0C);
+ else if (nDist < 0xd00 && nDist > 0xb00 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &tcherno13A9D4);
+ else if (nDist < 0xb00 && nDist > 0x500 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &tcherno13A9F0);
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &tcherno13A9B8);
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aitchern.h b/source/blood/src/aitchern.h
new file mode 100644
index 000000000..6065e2ccf
--- /dev/null
+++ b/source/blood/src/aitchern.h
@@ -0,0 +1,34 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE tchernobogIdle;
+extern AISTATE tchernobogSearch;
+extern AISTATE tchernobogChase;
+extern AISTATE tchernobogRecoil;
+extern AISTATE tcherno13A9B8;
+extern AISTATE tcherno13A9D4;
+extern AISTATE tcherno13A9F0;
+extern AISTATE tcherno13AA0C;
+extern AISTATE tcherno13AA28;
diff --git a/source/blood/src/aiunicult.cpp b/source/blood/src/aiunicult.cpp
new file mode 100644
index 000000000..e8a4fefd4
--- /dev/null
+++ b/source/blood/src/aiunicult.cpp
@@ -0,0 +1,1309 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+Copyright (C) NoOne
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aiunicult.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+#include "triggers.h"
+#include "endgame.h"
+
+static void GDXCultistAttack1(int, int);
+static void punchCallback(int, int);
+static void ThrowCallback1(int, int);
+static void ThrowCallback2(int, int);
+static void ThrowThing(int, bool);
+static void thinkSearch(spritetype*, XSPRITE*);
+static void thinkGoto(spritetype*, XSPRITE*);
+static void thinkChase(spritetype*, XSPRITE*);
+static void forcePunch(spritetype*, XSPRITE*);
+
+static int nGDXGenDudeAttack1 = seqRegisterClient(GDXCultistAttack1);
+static int nGDXGenDudePunch = seqRegisterClient(punchCallback);
+static int nGDXGenDudeThrow1 = seqRegisterClient(ThrowCallback1);
+static int nGDXGenDudeThrow2 = seqRegisterClient(ThrowCallback2);
+
+static bool gGDXGenDudePunch = false;
+
+//public static final int kSlopeThrow = -8192;
+AISTATE GDXGenDudeIdleL = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE GDXGenDudeIdleW = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE GDXGenDudeSearchL = { kAiStateSearch, 9, -1, 600, NULL, aiGenDudeMoveForward, thinkSearch, &GDXGenDudeIdleL };
+AISTATE GDXGenDudeSearchW= { kAiStateSearch, 13, -1, 600, NULL, aiGenDudeMoveForward, thinkSearch, &GDXGenDudeIdleW };
+AISTATE GDXGenDudeGotoL = { kAiStateMove, 9, -1, 600, NULL, aiGenDudeMoveForward, thinkGoto, &GDXGenDudeIdleL };
+AISTATE GDXGenDudeGotoW = { kAiStateMove, 13, -1, 600, NULL, aiGenDudeMoveForward, thinkGoto, &GDXGenDudeIdleW };
+AISTATE GDXGenDudeDodgeL = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &GDXGenDudeChaseL };
+AISTATE GDXGenDudeDodgeD = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge, NULL, &GDXGenDudeChaseD };
+AISTATE GDXGenDudeDodgeW = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge, NULL, &GDXGenDudeChaseW };
+// Dodge when get damage
+AISTATE GDXGenDudeDodgeDmgL = { kAiStateMove, 9, -1, 90, NULL, aiMoveDodge, NULL, &GDXGenDudeChaseL };
+AISTATE GDXGenDudeDodgeDmgD = { kAiStateMove, 14, -1, 90, NULL, aiMoveDodge, NULL, &GDXGenDudeChaseD };
+AISTATE GDXGenDudeDodgeDmgW = { kAiStateMove, 13, -1, 90, NULL, aiMoveDodge, NULL, &GDXGenDudeChaseW };
+// ---------------------
+AISTATE GDXGenDudeChaseL = { kAiStateChase, 9, -1, 0, NULL, aiGenDudeMoveForward, thinkChase, NULL };
+AISTATE GDXGenDudeChaseD = { kAiStateChase, 14, -1, 0, NULL, aiGenDudeMoveForward, thinkChase, NULL };
+AISTATE GDXGenDudeChaseW = { kAiStateChase, 13, -1, 0, NULL, aiGenDudeMoveForward, thinkChase, NULL };
+AISTATE GDXGenDudeFireL = { kAiStateChase, 6, nGDXGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &GDXGenDudeFireL };
+AISTATE GDXGenDudeFireD = { kAiStateChase, 8, nGDXGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &GDXGenDudeFireD };
+AISTATE GDXGenDudeFireW = { kAiStateChase, 8, nGDXGenDudeAttack1, 0, NULL, aiMoveTurn, thinkChase, &GDXGenDudeFireW };
+AISTATE GDXGenDudeFire2L = { kAiStateChase, 6, nGDXGenDudeAttack1, 0, NULL, NULL, thinkChase, &GDXGenDudeFire2L };
+AISTATE GDXGenDudeFire2D = { kAiStateChase, 8, nGDXGenDudeAttack1, 0, NULL, aiMoveTurn, NULL, &GDXGenDudeFire2D };
+AISTATE GDXGenDudeFire2W = { kAiStateChase, 8, nGDXGenDudeAttack1, 0, NULL, aiMoveTurn, NULL, &GDXGenDudeFire2W };
+AISTATE GDXGenDudeRecoilL = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &GDXGenDudeChaseL };
+AISTATE GDXGenDudeRecoilD = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &GDXGenDudeChaseD };
+AISTATE GDXGenDudeRecoilW = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &GDXGenDudeChaseW };
+AISTATE GDXGenDudeThrow = { kAiStateChase, 7, nGDXGenDudeThrow1, 0, NULL, NULL, NULL, &GDXGenDudeChaseL };
+AISTATE GDXGenDudeThrow2 = { kAiStateChase, 7, nGDXGenDudeThrow2, 0, NULL, NULL, NULL, &GDXGenDudeChaseL };
+AISTATE GDXGenDudeRTesla = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &GDXGenDudeDodgeL };
+AISTATE GDXGenDudeProne = { kAiStateIdle, 13, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE GDXGenDudePunch = { kAiStateChase,10, nGDXGenDudePunch, 0, NULL, NULL, forcePunch, &GDXGenDudeChaseL };
+AISTATE GDXGenDudeTurn = { kAiStateChase, 0, -1, 0, NULL, aiMoveTurn, thinkChase, NULL };
+
+
+static void forcePunch(spritetype* pSprite, XSPRITE* pXSprite) {
+ // Required for those who don't have fire trigger in punch seq and for default animation
+ if (gGDXGenDudePunch == false && seqGetStatus(3, pSprite->extra) == -1) {
+ int nXSprite = pSprite->extra;
+ punchCallback(0,nXSprite);
+ }
+
+ gGDXGenDudePunch = false;
+}
+
+
+static void punchCallback(int, int nXIndex){
+ XSPRITE* pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ spritetype* pSprite = &sprite[nSprite];
+
+ int nAngle = getangle(pXSprite->targetX - pSprite->x, pXSprite->targetY - pSprite->y);
+ int nZOffset1 = dudeInfo[pSprite->type - kDudeBase].eyeHeight/* * pSprite->yrepeat << 2*/;
+ int nZOffset2 = 0;
+ if(pXSprite->target != -1) {
+ spritetype* pTarget = &sprite[pXSprite->target];
+ if(IsDudeSprite(pTarget))
+ nZOffset2 = dudeInfo[pTarget->type - kDudeBase].eyeHeight/* * pTarget->yrepeat << 2*/;
+
+ int dx = Cos(nAngle) >> 16;
+ int dy = Sin(nAngle) >> 16;
+ int dz = nZOffset1 - nZOffset2;
+
+ if (!sfxPlayGDXGenDudeSound(pSprite,9,pXSprite->data3))
+ sfxPlay3DSound(pSprite, 530, 1, 0);
+
+ actFireVector(pSprite, 0, 0, dx, dy, dz,VECTOR_TYPE_22);
+ }
+
+ gGDXGenDudePunch = true;
+ }
+
+static void GDXCultistAttack1(int, int nXIndex) {
+ XSPRITE* pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ spritetype* pSprite = &sprite[nSprite];
+ int dx, dy, dz; int weapon = pXSprite->data1;
+ if (weapon >= 0 && weapon < kVectorMax) {
+
+ int vector = pXSprite->data1;
+ dx = Cos(pSprite->ang) >> 16;
+ dy = Sin(pSprite->ang) >> 16;
+ dz = gDudeSlope[nXIndex];
+
+ VECTORDATA* pVectorData = &gVectorData[vector];
+ int vdist = pVectorData->maxDist;
+
+ // dispersal modifiers here in case if non-melee enemy
+ if (vdist <= 0 || vdist > 1280) {
+ dx += Random3(3000 - 1000 * gGameOptions.nDifficulty);
+ dy += Random3(3000 - 1000 * gGameOptions.nDifficulty);
+ dz += Random3(1000 - 500 * gGameOptions.nDifficulty);
+ }
+
+ actFireVector(pSprite, 0, 0, dx, dy, dz,(VECTOR_TYPE)vector);
+ if (!sfxPlayGDXGenDudeSound(pSprite,7,pXSprite->data3))
+ sfxPlayVectorSound(pSprite,vector);
+
+ } else if (weapon >= kDudeBase && weapon < kDudeMax) {
+
+ spritetype* pSpawned = NULL; int dist = pSprite->clipdist * 6;
+ if ((pSpawned = actSpawnDude(pSprite, weapon, dist, 0)) == NULL)
+ return;
+
+ gDudeExtra[pSprite->extra].at6.u1.at4++;
+ pSpawned->owner = nSprite;
+ pSpawned->x += dist + (Random3(dist));
+ //pSpawned->z = pSprite->z;
+ //pSpawned->y = pSprite->y;
+ if (pSpawned->extra > -1) {
+ xsprite[pSpawned->extra].target = pXSprite->target;
+ if (pXSprite->target > -1)
+ aiActivateDude(pSpawned, &xsprite[pSpawned->extra]);
+ }
+ gKillMgr.sub_263E0(1);
+
+ if (!sfxPlayGDXGenDudeSound(pSprite, 7, pXSprite->data3))
+ sfxPlay3DSoundCP(pSprite, 379, 1, 0, 0x10000 - Random3(0x3000));
+
+ /*spritetype* pEffect = gFX.fxSpawn((FX_ID)52, pSpawned->sectnum, pSpawned->x, pSpawned->y, pSpawned->z, pSpawned->ang);
+ if (pEffect != NULL) {
+
+ pEffect->cstat = kSprOriginAlign | kSprFace;
+ pEffect->shade = -127;
+ switch (Random(3)) {
+ case 0:
+ pEffect->pal = 0;
+ break;
+ case 1:
+ pEffect->pal = 5;
+ break;
+ case 2:
+ pEffect->pal = 9;
+ break;
+ case 3:
+ pEffect->shade = 127;
+ pEffect->pal = 1;
+ default:
+ pEffect->pal = 6;
+ break;
+ }
+ int repeat = 64 + Random(50);
+ pEffect->xrepeat = repeat;
+ pEffect->yrepeat = repeat;
+ }*/
+
+ if (Chance(0x3500)) {
+ int state = checkAttackState(pSprite, pXSprite);
+ switch (state) {
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeW);
+ break;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeD);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeL);
+ break;
+ }
+ }
+ } else if (weapon >= kMissileBase && weapon < kMissileMax) {
+
+ dx = Cos(pSprite->ang) >> 16;
+ dy = Sin(pSprite->ang) >> 16;
+ dz = gDudeSlope[nXIndex];
+ //dz = 0;
+
+ // dispersal modifiers here
+ dx += Random3(3000 - 1000 * gGameOptions.nDifficulty);
+ dy += Random3(3000 - 1000 * gGameOptions.nDifficulty);
+ dz += Random3(1000 - 500 * gGameOptions.nDifficulty);
+
+ actFireMissile(pSprite, 0, 0, dx, dy, dz, weapon);
+ if (!sfxPlayGDXGenDudeSound(pSprite,7,pXSprite->data3))
+ sfxPlayMissileSound(pSprite, weapon);
+ }
+}
+
+static void ThrowCallback1(int, int nXIndex) {
+ ThrowThing(nXIndex, true);
+}
+
+static void ThrowCallback2(int, int nXIndex) {
+ ThrowThing(nXIndex, true);
+}
+
+static void ThrowThing(int nXIndex, bool impact) {
+ XSPRITE* pXSprite = &xsprite[nXIndex];
+ int nSprite = pXSprite->reference;
+ spritetype* pSprite = &sprite[nSprite];
+
+ if (!(pXSprite->target >= 0 && pXSprite->target < kMaxSprites))
+ return;
+
+ spritetype * pTarget = &sprite[pXSprite->target];
+ if (!(pTarget->type >= kDudeBase && pTarget->type < kDudeMax))
+ return;
+
+
+ int thingType = pXSprite->data1;
+ if (thingType >= kThingBase && thingType < kThingMax) {
+
+ THINGINFO* pThinkInfo = &thingInfo[thingType - kThingBase];
+ if (pThinkInfo->allowThrow == 1) {
+
+ if (!sfxPlayGDXGenDudeSound(pSprite, 8, pXSprite->data3))
+ sfxPlay3DSound(pSprite, 455, -1, 0);
+
+ int dx = pTarget->x - pSprite->x;
+ int dy = pTarget->y - pSprite->y;
+ int dz = pTarget->z - pSprite->z;
+
+ int dist = approxDist(dx, dy); int zThrow = 14500;
+ spritetype* pThing = NULL; spritetype* pLeech = NULL; XSPRITE* pXLeech = NULL;
+ if (thingType == kGDXThingCustomDudeLifeLeech) {
+ if ((pLeech = leechIsDropped(pSprite)) != NULL) {
+ // pickup life leech before throw it again
+ pXLeech = &xsprite[pLeech->extra];
+ removeLeech(pLeech);
+ }
+
+ zThrow = 5000;
+ }
+
+ pThing = actFireThing(pSprite, 0, 0, (dz / 128) - zThrow, thingType, divscale(dist / 540, 120, 23));
+ if (pThing == NULL) return;
+
+ if (pThinkInfo->at11 < 0 && pThing->type != kGDXThingThrowableRock) pThing->picnum = 0;
+ pThing->owner = pSprite->xvel;
+ switch (thingType) {
+ case 428:
+ impact = true;
+ pThing->xrepeat = 24;
+ pThing->yrepeat = 24;
+ xsprite[pThing->extra].data4 = 3 + gGameOptions.nDifficulty;
+ break;
+
+ case kGDXThingThrowableRock:
+ int sPics[6];
+ sPics[0] = 2406; sPics[1] = 2280;
+ sPics[2] = 2185; sPics[3] = 2155;
+ sPics[4] = 2620; sPics[5] = 3135;
+
+ pThing->picnum = sPics[Random(5)];
+ pThing->pal = 5;
+ pThing->xrepeat = 24 + Random(42);
+ pThing->yrepeat = 24 + Random(42);
+ pThing->cstat |= 0x0001;
+
+ if (Chance(0x3000)) pThing->cstat |= 0x0004;
+ if (Chance(0x3000)) pThing->cstat |= 0x0008;
+
+ if (pThing->xrepeat > 60) xsprite[pThing->extra].data1 = 43;
+ else if (pThing->xrepeat > 40) xsprite[pThing->extra].data1 = 33;
+ else if (pThing->xrepeat > 30) xsprite[pThing->extra].data1 = 23;
+ else xsprite[pThing->extra].data1 = 12;
+
+ impact = false;
+ return;
+ case 400:
+ case 401:
+ case 420:
+ impact = false;
+ break;
+ case kGDXThingTNTProx:
+ xsprite[pThing->extra].state = 0;
+ xsprite[pThing->extra].Proximity = true;
+ return;
+ case 431:
+ case kGDXThingCustomDudeLifeLeech:
+ XSPRITE* pXThing = &xsprite[pThing->extra];
+ if (pLeech != NULL) pXThing->health = pXLeech->health;
+ else pXThing->health = 300 * gGameOptions.nDifficulty;
+
+ sfxPlay3DSound(pSprite, 490, -1, 0);
+
+ if (gGameOptions.nDifficulty <= 2) pXThing->data3 = 32700;
+ else pXThing->data3 = Random(10);
+ pThing->pal = 6;
+ pXThing->target = pTarget->xvel;
+ pXThing->Proximity = true;
+ pXThing->stateTimer = 1;
+ evPost(pThing->xvel, 3, 80, CALLBACK_ID_20);
+ return;
+ }
+
+ if (impact == true && dist <= 7680) xsprite[pThing->extra].Impact = true;
+ else {
+ xsprite[pThing->extra].Impact = false;
+ evPost(pThing->xvel, 3, 120 * Random(2) + 120, COMMAND_ID_1);
+ }
+ return;
+ }
+
+ }
+}
+
+static void thinkSearch( spritetype* pSprite, XSPRITE* pXSprite )
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ sub_5F15C(pSprite, pXSprite);
+
+}
+
+static void thinkGoto( spritetype* pSprite, XSPRITE* pXSprite )
+{
+ int dx, dy, dist;
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->lotag - kDudeBase];
+
+ dx = pXSprite->targetX - pSprite->x;
+ dy = pXSprite->targetY - pSprite->y;
+
+ int nAngle = getangle(dx, dy);
+ dist = approxDist(dx, dy);
+
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+
+ // if reached target, change to search mode
+ if ( /*dist < M2X(10.0)*/ dist < 5120 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery ) {
+ if(spriteIsUnderwater(pSprite,false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchW);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchL);
+ }
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase( spritetype* pSprite, XSPRITE* pXSprite )
+{
+
+
+ if (Chance(0x3000)) GDXGenDudeRecoilL.at18 = &GDXGenDudeDodgeD;
+ else GDXGenDudeRecoilL.at18 = &GDXGenDudeChaseL;
+
+ if (Chance(0x3000)) GDXGenDudeRecoilW.at18 = &GDXGenDudeDodgeW;
+ else GDXGenDudeRecoilW.at18 = &GDXGenDudeChaseW;
+
+ if (Chance(0x3000)) GDXGenDudeRecoilD.at18 = &GDXGenDudeDodgeL;
+ else GDXGenDudeRecoilD.at18 = &GDXGenDudeChaseL;
+
+ if (pXSprite->target == -1) {
+ if(spriteIsUnderwater(pSprite,false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeGotoW);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeGotoL);
+ return;
+ }
+
+ int dx, dy, dist;
+
+
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype* pTarget = &sprite[pXSprite->target];
+ XSPRITE* pXTarget = pTarget->extra > 0 ? &xsprite[pTarget->extra] : NULL;
+ if(!IsDudeSprite(pTarget))
+ pXTarget = NULL;
+
+ // check target
+ dx = pTarget->x - pSprite->x;
+ dy = pTarget->y - pSprite->y;
+
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+
+ if ( pXTarget == NULL || pXTarget->health <= 0 )
+ {
+ // target is dead
+ if(spriteIsUnderwater(pSprite,false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchW);
+ else {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchL);
+ sfxPlayGDXGenDudeSound(pSprite,5,pXSprite->data3);
+ }
+
+ return;
+ }
+
+ if ( IsPlayerSprite( pTarget ) )
+ {
+ PLAYER* pPlayer = &gPlayer[ pTarget->type - kDudePlayer1 ];
+ if ( powerupCheck( pPlayer, 13 ) > 0 )
+ {
+ if(spriteIsUnderwater(pSprite,false))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchW);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchL);
+ return;
+ }
+ }
+
+ dist = approxDist(dx, dy);
+ if ( dist <= pDudeInfo->seeDist ) {
+ int nAngle = getangle(dx, dy);
+ int losAngle = ((1024 + nAngle - pSprite->ang) & 2047) - 1024;
+ int eyeAboveZ = (pDudeInfo->eyeHeight * pSprite->yrepeat) << 2;
+ VECTORDATA* meleeVector = &gVectorData[22];
+
+ // is there a line of sight to the target?
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pSprite->x, pSprite->y, pSprite->z - eyeAboveZ, pSprite->sectnum)){
+ // is the target visible?
+
+ if (dist < pDudeInfo->seeDist && klabs(losAngle) <= pDudeInfo->periphery) {
+ aiSetTarget(pXSprite, pXSprite->target);
+
+ if ((gFrameClock & 64) == 0 && Chance(0x1000) && !spriteIsUnderwater(pSprite,false))
+ sfxPlayGDXGenDudeSound(pSprite,6,pXSprite->data3);
+
+ if (dist <= 1500) gDudeSlope[sprite[pXSprite->reference].extra] = divscale(pTarget->z - pSprite->z, dist, 13);
+ else if (dist <= 3000) gDudeSlope[sprite[pXSprite->reference].extra] = divscale(pTarget->z - pSprite->z, dist, 11);
+ else if (dist > 0) gDudeSlope[sprite[pXSprite->reference].extra] = divscale(pTarget->z - pSprite->z, dist, 10);
+
+ spritetype* pLeech = NULL;
+ if (pXSprite->data1 >= kThingBase && pXSprite->data1 < kThingMax) {
+ if (pXSprite->data1 == 431) pXSprite->data1 = kGDXThingCustomDudeLifeLeech;
+ if (klabs(losAngle) < kAng15) {
+ if (dist < 12264 && dist > 7680 && !spriteIsUnderwater(pSprite,false) && pXSprite->data1 != kGDXThingCustomDudeLifeLeech){
+ int pHit = HitScan(pSprite, pSprite->z, dx, dy, 0, 16777280, 0);
+ switch(pHit){
+ case 3:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow);
+ return;
+
+ case 0:
+ case 4:
+ return;
+ case -1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow);
+ return;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow);
+ return;
+ }
+
+ } else if (dist > 4072 && dist <= 9072 && !spriteIsUnderwater(pSprite,false) && pSprite->owner != kMaxSprites){
+ if (pXSprite->data1 != kGDXThingCustomDudeLifeLeech && pXSprite->data1 != kGDXThingThrowableRock) {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow2);
+ } else {
+
+ if (pXSprite->data1 == kGDXThingCustomDudeLifeLeech) {
+ if ((pLeech = leechIsDropped(pSprite)) == NULL){
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow2);
+ GDXGenDudeThrow2.at18 = &GDXGenDudeDodgeL;
+ return;
+ }
+
+ XSPRITE* pXLeech = &xsprite[pLeech->extra];
+ int ldist = getTargetDist(pTarget,pDudeInfo,pLeech);
+ if (ldist > 3 || !cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum,
+ pLeech->x, pLeech->y, pLeech->z, pLeech->sectnum) || pXLeech->target == -1) {
+
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow2);
+ GDXGenDudeThrow2.at18 = &GDXGenDudeDodgeL;
+
+ } else {
+ GDXGenDudeThrow2.at18 = &GDXGenDudeChaseL;
+ if (pXLeech->target != pXSprite->target) pXLeech->target = pXSprite->target;
+ if (dist > 5072 && Chance(0x3000)) {
+ if (Chance(0x2000))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeL);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeD);
+ } else {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+ }
+ }
+
+ } else if (pXSprite->data1 == kGDXThingThrowableRock){
+ if (Chance(0x2000))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeThrow2);
+ else
+ sfxPlayGDXGenDudeSound(pSprite,0,pXSprite->data3);
+ }
+ }
+
+ return;
+
+ } else if (dist <= meleeVector->maxDist) {
+ if (spriteIsUnderwater(pSprite,false)) {
+ if (Chance(0x7000))
+ aiNewState(pSprite, pXSprite, &GDXGenDudePunch);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeW);
+
+ } else {
+ if (Chance(0x7000))
+ aiNewState(pSprite, pXSprite, &GDXGenDudePunch);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeL);
+
+ }
+ return;
+
+ } else {
+ int state = checkAttackState(pSprite, pXSprite);
+
+ if(state == 1)
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseW);
+
+ if(state == 2) {
+ if (Chance(0x3000))
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseD);
+ else
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+ }
+
+ if(state == 3)
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+
+ return;
+ }
+ }
+
+ } else {
+
+ int defDist = 17920; int vdist = defDist;
+ if (pXSprite->data1 > 0 && pXSprite->data1 < kVectorMax) {
+
+ switch (pXSprite->data1) {
+ case 19:
+ pXSprite->data1 = 2;
+ break;
+ }
+
+ VECTORDATA* pVectorData = &gVectorData[pXSprite->data1];
+ vdist = pVectorData->maxDist;
+ if (vdist <= 0 || vdist > defDist)
+ vdist = defDist;
+
+ } else if (pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeMax && pXSprite->data1 != kGDXDudeUniversalCultist) {
+
+ if (gDudeExtra[pSprite->extra].at6.u1.at4 > 0) {
+
+ updateTargetOfSlaves(pSprite);
+
+ if (pXSprite->target >= 0 && sprite[pXSprite->target].owner == pSprite->xvel) {
+ aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
+ return;
+ }
+ }
+
+ if (gDudeExtra[pSprite->extra].at6.u1.at4 <= gGameOptions.nDifficulty && dist > meleeVector->maxDist) {
+ vdist = (vdist / 2) + Random(vdist / 2);
+ }
+ else if (dist <= meleeVector->maxDist) {
+ aiNewState(pSprite, pXSprite, &GDXGenDudePunch);
+ return;
+ }
+ else {
+ int state = checkAttackState(pSprite, pXSprite);
+ switch (state) {
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseW);
+ return;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseD);
+ return;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+ return;
+ }
+ }
+ } else if (pXSprite->data1 >= kMissileBase && pXSprite->data1 < kMissileMax){
+ // special handling for flame, explosive and life leech missiles
+ int mdist = 2500;
+ if (pXSprite->data1 != 303) mdist = 3000;
+ switch(pXSprite->data1){
+ case 315:
+ // Pickup life leech if it was thrown previously
+ if ((pLeech = leechIsDropped(pSprite)) != NULL)
+ removeLeech(pLeech);
+ break;
+ case 303:
+ case 305:
+ case 312:
+ case 313:
+ case 314:
+ {
+ if (dist > mdist || pXSprite->locked == 1) break;
+ int state = checkAttackState(pSprite, pXSprite);
+ if (dist <= meleeVector->maxDist && Chance(0x7000)) {
+ aiNewState(pSprite, pXSprite, &GDXGenDudePunch);
+ return;
+ } else {
+ switch (state) {
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseW);
+ return;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseD);
+ return;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+ return;
+ }
+ }
+ }
+ case 304:
+ case 308:
+ {
+ if (spriteIsUnderwater(pSprite, false)) {
+ if (dist <= meleeVector->maxDist) {
+ if (Chance(0x4000)) {
+ aiNewState(pSprite, pXSprite, &GDXGenDudePunch);
+ }
+ else {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeW);
+ }
+ }
+ else {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseW);
+ }
+ return;
+ }
+
+ vdist = 4200;
+ if ((gFrameClock & 16) == 0)
+ vdist += Random(800);
+ break;
+ }
+ }
+ } else if (pXSprite->data1 >= 459 && pXSprite->data1 < (459 + kExplodeMax) - 1) {
+
+ int nType = pXSprite->data1 - 459; EXPLOSION* pExpl = &explodeInfo[nType];
+ if (pExpl != NULL &&
+ CheckProximity(pSprite, pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pExpl->at3 / 2) &&
+ doExplosion(pSprite, nType)) {
+
+ pXSprite->health = 1;
+ actDamageSprite(pSprite->xvel, pSprite, DAMAGE_TYPE_3, 65535);
+
+ xvel[pSprite->xvel] = 0;
+ zvel[pSprite->xvel] = 0;
+ yvel[pSprite->xvel] = 0;
+ }
+ return;
+
+ } else {
+
+ // Scared dude - no weapon. Still can punch you sometimes.
+ int state = checkAttackState(pSprite, pXSprite);
+ if (Chance(0x0500) && !spriteIsUnderwater(pSprite,false))
+ sfxPlayGDXGenDudeSound(pSprite,6,pXSprite->data3);
+
+ if (Chance(0x5000)){
+ switch (state){
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeW);
+ break;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeD);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeDodgeL);
+ break;
+ }
+
+ pXSprite->target = -1;
+
+ } else if (dist <= meleeVector->maxDist && Chance(0x7000)) {
+ aiNewState(pSprite, pXSprite, &GDXGenDudePunch);
+ } else {
+ switch (state){
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchW);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeSearchL);
+ break;
+ }
+
+ pXSprite->target = -1;
+ }
+
+ return;
+ }
+
+ int state = checkAttackState(pSprite, pXSprite);
+ if (dist > vdist && pXSprite->aiState == &GDXGenDudeChaseD)
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+
+ if (dist < vdist && klabs(losAngle) < 35 /*&& klabs(losAngle) < kAngle5*/) {
+ if (vdist > 1512){
+ switch(state){
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeFireW);
+ pXSprite->aiState->at18 = &GDXGenDudeFireW;
+ break;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeFireD);
+ pXSprite->aiState->at18 = &GDXGenDudeFireD;
+ break;
+
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeFireL);
+ pXSprite->aiState->at18 = &GDXGenDudeFireL;
+ break;
+ }
+ } else {
+ switch(state){
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeFire2W);
+ pXSprite->aiState->at18 = &GDXGenDudeFire2W;
+ break;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeFire2D);
+ pXSprite->aiState->at18 = &GDXGenDudeFire2D;
+ break;
+
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeFire2L);
+ pXSprite->aiState->at18 = &GDXGenDudeFire2L;
+ break;
+ }
+ }
+ } else {
+ int nSeq = 6; if (state < 3) nSeq = 8;
+ if (seqGetID(3, pSprite->extra) == pDudeInfo->seqStartID + nSeq) {
+ switch(state){
+ case 1:
+ pXSprite->aiState->at18 = &GDXGenDudeChaseW;
+ break;
+ case 2:
+ pXSprite->aiState->at18 = &GDXGenDudeChaseD;
+ break;
+ default:
+ pXSprite->aiState->at18 = &GDXGenDudeChaseL;
+ break;
+ }
+ } else {
+ if(pXSprite->aiState == &GDXGenDudeChaseL || pXSprite->aiState == &GDXGenDudeChaseD ||
+ pXSprite->aiState == &GDXGenDudeChaseW || pXSprite->aiState == &GDXGenDudeFireL ||
+ pXSprite->aiState == &GDXGenDudeFireD || pXSprite->aiState == &GDXGenDudeFireW)
+ return;
+
+ switch (state) {
+ case 1:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseW);
+ pXSprite->aiState->at18 = &GDXGenDudeFireW;
+ break;
+ case 2:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseD);
+ pXSprite->aiState->at18 = &GDXGenDudeFireD;
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &GDXGenDudeChaseL);
+ pXSprite->aiState->at18 = &GDXGenDudeFireL;
+ break;
+ }
+ }
+ }
+
+ }
+ return;
+ }
+ }
+ }
+
+ if(spriteIsUnderwater(pSprite,false)) {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeGotoW);
+ } else {
+ aiNewState(pSprite, pXSprite, &GDXGenDudeGotoL);
+ }
+ pXSprite->target = -1;
+}
+
+int checkAttackState(spritetype* pSprite, XSPRITE* pXSprite) {
+ UNREFERENCED_PARAMETER(pXSprite);
+ if (sub_5BDA8(pSprite, 14) || spriteIsUnderwater(pSprite,false))
+ {
+ if ( !sub_5BDA8(pSprite, 14) || spriteIsUnderwater(pSprite,false))
+ {
+ if (spriteIsUnderwater(pSprite,false))
+ {
+ return 1; //water
+ }
+ }
+ else
+ {
+ return 2; //duck
+ }
+ }
+ else
+ {
+ return 3; //land
+ }
+ return 0;
+}
+
+/*bool sub_5BDA8(spritetype* pSprite, int nSeq)
+{
+ if (pSprite->statnum == 6 && pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ {
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ if (seqGetID(3, pSprite->extra) == pDudeInfo->seqStartID + nSeq && seqGetStatus(3, pSprite->extra) >= 0)
+ return true;
+ }
+ return false;
+}
+
+bool sub_57901(spritetype* pSprite, int nSeqID) {
+ if ( pSprite->statnum == 6 )
+ {
+ if ( IsDudeSprite(pSprite) )
+ {
+ SEQINST* pSeqInst = GetInstance(3, pSprite->extra); Seq* pSeq = pSeqInst->pSequence;
+ if ( pSeq == pSEQs.get(xsprite[pSprite->extra].data2 + nSeqID) && seqGetStatus(3, pSprite->extra) >= 0 )
+ return true;
+ }
+ }
+ return false;
+}*/
+
+bool TargetNearThing(spritetype* pSprite, int thingType) {
+ for ( int nSprite = headspritesect[pSprite->sectnum]; nSprite >= 0; nSprite = nextspritesect[nSprite] )
+ {
+ // check for required things or explosions in the same sector as the target
+ if ( sprite[nSprite].type == thingType || sprite[nSprite].statnum == 2 )
+ return true; // indicate danger
+ }
+ return false;
+}
+
+///// For gen dude
+int getGenDudeMoveSpeed(spritetype* pSprite,int which, bool mul, bool shift) {
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ XSPRITE* pXSprite = &xsprite[pSprite->extra];
+ int speed = -1; int step = 2500; int maxSpeed = 146603;
+ switch(which){
+ case 0:
+ speed = pDudeInfo->frontSpeed;
+ break;
+ case 1:
+ speed = pDudeInfo->sideSpeed;
+ break;
+ case 2:
+ speed = pDudeInfo->backSpeed;
+ break;
+ case 3:
+ speed = pDudeInfo->angSpeed;
+ break;
+ default:
+ return -1;
+ }
+ if (pXSprite->busyTime > 0) speed /=3;
+ if (speed > 0 && mul) {
+
+ //System.err.println(pXSprite.busyTime);
+ if (pXSprite->busyTime > 0)
+ speed += (step * pXSprite->busyTime);
+ }
+
+ if (shift) speed *= 4 >> 4;
+ if (speed > maxSpeed) speed = maxSpeed;
+
+ return speed;
+}
+
+void aiGenDudeMoveForward(spritetype* pSprite, XSPRITE* pXSprite ) {
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+
+ int maxTurn = pDudeInfo->angSpeed * 4 >> 4;
+
+ int dang = ((kAng180 + pXSprite->goalAng - pSprite->ang) & 2047) - kAng180;
+ pSprite->ang = ((pSprite->ang + ClipRange(dang, -maxTurn, maxTurn)) & 2047);
+
+ // don't move forward if trying to turn around
+ if ( klabs(dang) > kAng60 )
+ return;
+
+ int sin = Sin(pSprite->ang);
+ int cos = Cos(pSprite->ang);
+
+ int frontSpeed = getGenDudeMoveSpeed(pSprite,0,true,false);
+ xvel[pSprite->xvel] += mulscale(cos, frontSpeed, 30);
+ yvel[pSprite->xvel] += mulscale(sin, frontSpeed, 30);
+}
+
+bool sfxPlayGDXGenDudeSound(spritetype* pSprite, int mode, int data) {
+ int sndId = -1; int rand = 0; bool gotSnd = true;
+
+ switch (mode){
+ // spot sound
+ case 0:
+ rand = 2; sndId = 1003;
+ if (data > 0)
+ sndId = data;
+ break;
+ // pain sound
+ case 1:
+ rand = 2; sndId = 1013;
+ if (data > 0)
+ sndId = data + 2;
+ break;
+ // death sound
+ case 2:
+ rand = 2; sndId = 1018;
+ if (data > 0)
+ sndId = data + 4;
+ break;
+ // burning state sound
+ case 3:
+ rand = 2; sndId = 1031;
+ if (data > 0)
+ sndId = data + 6;
+ break;
+ // explosive death or end of burning state sound
+ case 4:
+ rand = 2; sndId = 1018;
+ if (data > 0)
+ sndId = data + 8;
+ break;
+ // target of dude is dead
+ case 5:
+ rand = 2; sndId = 4021;
+ if (data > 0)
+ sndId = data + 10;
+ break;
+ // roam sounds
+ case 6:
+ rand = 2; sndId = 1005;
+ if (data > 0)
+ sndId = data + 12;
+ break;
+ // weapon attack
+ case 7:
+ if (data > 0)
+ sndId = data + 14;
+ break;
+ // throw attack
+ case 8:
+ if (data > 0)
+ sndId = data + 15;
+ break;
+ // melee attack
+ case 9:
+ if (data > 0)
+ sndId = data + 16;
+ break;
+ // transforming in other dude
+ case 10:
+ sndId = 9008;
+ if (data > 0)
+ sndId = data + 17;
+ break;
+ default:
+ return false;
+
+ }
+
+
+ if (sndId < 0) return false;
+ else if (data <= 0) sndId = sndId + Random(rand);
+ else {
+
+ int maxRetries = 5; gotSnd = false;
+ // Let's try to get random snd
+ while(maxRetries-- > 0){
+ int random = Random(rand);
+ if (gSysRes.Lookup(sndId + random, "SFX")){
+ sndId = sndId + random;
+ gotSnd = true;
+ break;
+ }
+ }
+
+ // If no success in getting random snd, get first existing one
+ if (gotSnd == false){
+ int max = sndId + rand;
+ while(sndId++ <= max){
+ if (gSysRes.Lookup(sndId, "SFX")) {
+ gotSnd = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (gotSnd) {
+ switch (mode){
+ // case 1:
+ case 2:
+ case 4:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ sfxPlay3DSound(pSprite, sndId, -1, 0);
+ break;
+ default:
+ aiPlay3DSound(pSprite, sndId, AI_SFX_PRIORITY_2, -1);
+ break;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+
+bool spriteIsUnderwater(spritetype* pSprite,bool oldWay) {
+ if (oldWay){
+ if (xsprite[pSprite->extra].medium == 1 || xsprite[pSprite->extra].medium == 2)
+ return true;
+ return false;
+ }
+
+ if (sector[pSprite->sectnum].extra < 0) return false;
+ else if (xsector[sector[pSprite->sectnum].extra].Underwater)
+ return true;
+
+ return false;
+}
+
+spritetype* leechIsDropped(spritetype* pSprite) {
+ for (int nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ if (sprite[nSprite].lotag == kGDXThingCustomDudeLifeLeech && sprite[nSprite].owner == pSprite->xvel)
+ return &sprite[nSprite];
+ }
+
+ return NULL;
+
+}
+
+void removeDudeStuff(spritetype* pSprite) {
+ for (short nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ if (sprite[nSprite].owner != pSprite->xvel) continue;
+ switch (sprite[nSprite].lotag) {
+ case 401:
+ case 402:
+ case 433:
+ deletesprite(nSprite);
+ break;
+ case kGDXThingCustomDudeLifeLeech:
+ killDudeLeech(&sprite[nSprite]);
+ break;
+ }
+ }
+
+ for (short nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ if (sprite[nSprite].owner != pSprite->xvel) continue;
+ actDamageSprite(sprite[nSprite].owner, &sprite[nSprite], (DAMAGE_TYPE) 0, 65535);
+ }
+}
+
+void removeLeech(spritetype* pLeech, bool delSprite) {
+ if (pLeech != NULL) {
+ spritetype* pEffect = gFX.fxSpawn((FX_ID)52,pLeech->sectnum,pLeech->x,pLeech->y,pLeech->z,pLeech->ang);
+ if (pEffect != NULL) {
+ pEffect->cstat = kSprFace;
+ pEffect->pal = 6;
+ int repeat = 64 + Random(50);
+ pEffect->xrepeat = repeat;
+ pEffect->yrepeat = repeat;
+ }
+ sfxPlay3DSoundCP(pLeech, 490, -1, 0,60000);
+ if (delSprite)
+ actPostSprite(pLeech->index, kStatFree);
+ }
+}
+
+void killDudeLeech(spritetype* pLeech) {
+ if (pLeech != NULL) {
+ //removeLeech(pLeech, false);
+ actDamageSprite(pLeech->owner, pLeech, DAMAGE_TYPE_3, 65535);
+ sfxPlay3DSoundCP(pLeech, 522, -1, 0, 60000);
+ }
+}
+
+XSPRITE* getNextIncarnation(XSPRITE* pXSprite) {
+ if (pXSprite->txID > 0) {
+ for (short nSprite = headspritestat[7]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ if (!IsDudeSprite(&sprite[nSprite]) || sprite[nSprite].extra < 0) continue;
+ if (xsprite[sprite[nSprite].extra].rxID == pXSprite->txID && xsprite[sprite[nSprite].extra].health > 0)
+ return &xsprite[sprite[nSprite].extra];
+ }
+ }
+ return NULL;
+}
+
+bool dudeIsMelee(XSPRITE* pXSprite) {
+ int meleeDist = 2048; int vdist = meleeDist;
+ if (pXSprite->data1 >= 0 && pXSprite->data1 < kVectorMax) {
+ int vector = pXSprite->data1; if (vector <= 0) vector = 2;
+ VECTORDATA pVectorData = gVectorData[vector];
+ vdist = pVectorData.maxDist;
+
+ if (vdist > 0 && vdist <= meleeDist)
+ return true;
+
+ }
+ else {
+
+ if (pXSprite->data1 >= 459 && pXSprite->data1 < (459 + kExplodeMax) - 1)
+ return true;
+
+ switch (pXSprite->data1) {
+ case 304:
+ case 308:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+int getRecoilChance(spritetype* pSprite) {
+ int mass = getDudeMassBySpriteSize(pSprite);
+ int cumulDmg = 0; int baseChance = 0x4000;
+ if (pSprite->extra >= 0) {
+ XSPRITE pXSprite = xsprite[pSprite->extra];
+ baseChance += (pXSprite.burnTime / 2);
+ cumulDmg = pXSprite.cumulDamage;
+ if (dudeIsMelee(&pXSprite))
+ baseChance = 0x500;
+ }
+
+ baseChance += cumulDmg;
+ int chance = ((baseChance / mass) << 7);
+ return chance;
+
+}
+
+int getDodgeChance(spritetype* pSprite) {
+ int mass = getDudeMassBySpriteSize(pSprite); int baseChance = 0x1000;
+ if (pSprite->extra >= 0) {
+ XSPRITE pXSprite = xsprite[pSprite->extra];
+ baseChance += pXSprite.burnTime;
+ if (dudeIsMelee(&pXSprite))
+ baseChance = 0x200;
+ }
+
+ int chance = ((baseChance / mass) << 7);
+ return chance;
+}
+
+void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT a3)
+{
+ if (a3.cmd == COMMAND_ID_0) {
+ actPostSprite(pSprite->xvel, kStatFree);
+ return;
+ }
+
+ int nTarget = pXSprite->target;
+ if (nTarget >= 0 && nTarget < kMaxSprites) {
+ spritetype* pTarget = &sprite[nTarget];
+ if (pTarget->statnum == 6 && !(pTarget->hitag & 32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites && !pXSprite->stateTimer)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int nType = pTarget->type - kDudeBase;
+ DUDEINFO* pDudeInfo = &dudeInfo[nType];
+ int z1 = (top - pSprite->z) - 256;
+ int x = pTarget->x; int y = pTarget->y; int z = pTarget->z;
+ int nDist = approxDist(x - pSprite->x, y - pSprite->y);
+
+ if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum))
+ {
+ int t = divscale(nDist, 0x1aaaaa, 12);
+ x += (xvel[nTarget] * t) >> 12;
+ y += (yvel[nTarget] * t) >> 12;
+ int angBak = pSprite->ang;
+ pSprite->ang = getangle(x - pSprite->x, y - pSprite->y);
+ int dx = Cos(pSprite->ang) >> 16;
+ int dy = Sin(pSprite->ang) >> 16;
+ int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
+ int dz = divscale(tz - top - 256, nDist, 10);
+ int nMissileType = 316 + (pXSprite->data3 ? 1 : 0);
+ int t2;
+
+ if (!pXSprite->data3) t2 = 120 / 10.0;
+ else t2 = (3 * 120) / 10.0;
+
+ spritetype * pMissile = actFireMissile(pSprite, 0, z1, dx, dy, dz, nMissileType);
+ if (pMissile)
+ {
+ pMissile->owner = pSprite->owner;
+ pXSprite->stateTimer = 1;
+ evPost(pSprite->index, 3, t2, CALLBACK_ID_20);
+ pXSprite->data3 = ClipLow(pXSprite->data3 - 1, 0);
+ }
+ pSprite->ang = angBak;
+ }
+ }
+
+ }
+}
+
+bool doExplosion(spritetype* pSprite, int nType) {
+ spritetype* pExplosion = actSpawnSprite(pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 2, true);
+ int nSeq = 4; int nSnd = 304; EXPLOSION* pExpl = &explodeInfo[nType];
+
+ pExplosion->yrepeat = pExpl->at0;
+ pExplosion->xrepeat = pExpl->at0;
+ pExplosion->lotag = nType;
+ pExplosion->cstat |= kSprInvisible | kSprOriginAlign;
+ pExplosion->owner = pSprite->xvel;
+
+ if (pExplosion->extra >= 0) {
+ xsprite[pExplosion->extra].target = 0;
+ xsprite[pExplosion->extra].data1 = pExpl->atf;
+ xsprite[pExplosion->extra].data2 = pExpl->at13;
+ xsprite[pExplosion->extra].data3 = pExpl->at17;
+
+
+ if (nType == 0) { nSeq = 3; nSnd = 303; pExplosion->z = pSprite->z; }
+ else if (nType == 2) { nSeq = 4; nSnd = 305; }
+ else if (nType == 3) { nSeq = 9; nSnd = 307; }
+ else if (nType == 4) { nSeq = 5; nSnd = 307; }
+ else if (nType <= 6) { nSeq = 4; nSnd = 303; }
+ else if (nType == 7) { nSeq = 4; nSnd = 303; }
+
+
+ if (fileExistsRFF(nSeq, "SEQ")) seqSpawn(nSeq, 3, pExplosion->extra, -1);
+ sfxPlay3DSound(pExplosion, nSnd, -1, 0);
+
+ return true;
+ }
+
+ return false;
+}
+
+
+void updateTargetOfSlaves(spritetype* pSprite) {
+ for (short nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ if (sprite[nSprite].owner != pSprite->xvel || sprite[nSprite].extra < 0 || !IsDudeSprite(&sprite[nSprite])) continue;
+ else if (xsprite[pSprite->extra].target != xsprite[sprite[nSprite].extra].target
+ && IsDudeSprite(&sprite[xsprite[pSprite->extra].target])) {
+ aiSetTarget(&xsprite[sprite[nSprite].extra], xsprite[pSprite->extra].target);
+ }
+
+ if (xsprite[sprite[nSprite].extra].target >= 0) {
+ // don't attack mates
+ if (sprite[xsprite[sprite[nSprite].extra].target].owner == sprite[nSprite].owner)
+ aiSetTarget(&xsprite[sprite[nSprite].extra], pSprite->x, pSprite->y, pSprite->z);
+ }
+
+ if (!isActive(sprite[nSprite].xvel) && xsprite[sprite[nSprite].extra].target >= 0)
+ aiActivateDude(&sprite[nSprite], &xsprite[sprite[nSprite].extra]);
+ }
+
+ return;
+}
+//////////
diff --git a/source/blood/src/aiunicult.h b/source/blood/src/aiunicult.h
new file mode 100644
index 000000000..da9a64284
--- /dev/null
+++ b/source/blood/src/aiunicult.h
@@ -0,0 +1,75 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+Copyright (C) NoOne
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+#include "eventq.h"
+
+extern AISTATE GDXGenDudeIdleL;
+extern AISTATE GDXGenDudeIdleW;
+extern AISTATE GDXGenDudeSearchL;
+extern AISTATE GDXGenDudeSearchW;
+extern AISTATE GDXGenDudeGotoL;
+extern AISTATE GDXGenDudeGotoW;
+extern AISTATE GDXGenDudeDodgeL;
+extern AISTATE GDXGenDudeDodgeD;
+extern AISTATE GDXGenDudeDodgeW;
+extern AISTATE GDXGenDudeDodgeDmgL;
+extern AISTATE GDXGenDudeDodgeDmgD;
+extern AISTATE GDXGenDudeDodgeDmgW;
+extern AISTATE GDXGenDudeChaseL;
+extern AISTATE GDXGenDudeChaseD;
+extern AISTATE GDXGenDudeChaseW;
+extern AISTATE GDXGenDudeFireL;
+extern AISTATE GDXGenDudeFireD;
+extern AISTATE GDXGenDudeFireW;
+extern AISTATE GDXGenDudeFire2L;
+extern AISTATE GDXGenDudeFire2D;
+extern AISTATE GDXGenDudeFire2W;
+extern AISTATE GDXGenDudeRecoilL;
+extern AISTATE GDXGenDudeRecoilD;
+extern AISTATE GDXGenDudeRecoilW;
+extern AISTATE GDGenDudeThrow;
+extern AISTATE GDGenDudeThrow2;
+extern AISTATE GDXGenDudePunch;
+extern AISTATE GDXGenDudeRTesla;
+extern AISTATE GDXGenDudeProne;
+extern AISTATE GDXGenDudeTurn;
+
+XSPRITE* getNextIncarnation(XSPRITE* pXSprite);
+void killDudeLeech(spritetype* pLeech);
+void removeLeech(spritetype* pLeech, bool delSprite = true);
+void removeDudeStuff(spritetype* pSprite);
+spritetype* leechIsDropped(spritetype* pSprite);
+bool spriteIsUnderwater(spritetype* pSprite, bool oldWay);
+bool sfxPlayGDXGenDudeSound(spritetype* pSprite, int mode, int data);
+void aiGenDudeMoveForward(spritetype* pSprite, XSPRITE* pXSprite);
+int getGenDudeMoveSpeed(spritetype* pSprite, int which, bool mul, bool shift);
+bool TargetNearThing(spritetype* pSprite, int thingType);
+int checkAttackState(spritetype* pSprite, XSPRITE* pXSprite);
+bool doExplosion(spritetype* pSprite, int nType);
+void dudeLeechOperate(spritetype* pSprite, XSPRITE* pXSprite, EVENT a3);
+int getDodgeChance(spritetype* pSprite);
+int getRecoilChance(spritetype* pSprite);
+bool dudeIsMelee(XSPRITE* pXSprite);
+void updateTargetOfSlaves(spritetype* pSprite);
\ No newline at end of file
diff --git a/source/blood/src/aizomba.cpp b/source/blood/src/aizomba.cpp
new file mode 100644
index 000000000..dab725a83
--- /dev/null
+++ b/source/blood/src/aizomba.cpp
@@ -0,0 +1,281 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aizomba.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void HackSeqCallback(int, int);
+static void StandSeqCallback(int, int);
+static void thinkSearch(spritetype *, XSPRITE *);
+static void thinkGoto(spritetype *, XSPRITE *);
+static void thinkChase(spritetype *, XSPRITE *);
+static void thinkPonder(spritetype *, XSPRITE *);
+static void myThinkTarget(spritetype *, XSPRITE *);
+static void myThinkSearch(spritetype *, XSPRITE *);
+static void entryEZombie(spritetype *, XSPRITE *);
+static void entryAIdle(spritetype *, XSPRITE *);
+static void entryEStand(spritetype *, XSPRITE *);
+
+static int nHackClient = seqRegisterClient(HackSeqCallback);
+static int nStandClient = seqRegisterClient(StandSeqCallback);
+
+AISTATE zombieAIdle = { kAiStateIdle, 0, -1, 0, entryAIdle, NULL, aiThinkTarget, NULL };
+AISTATE zombieAChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieAPonder = { kAiStateOther, 0, -1, 0, NULL, aiMoveTurn, thinkPonder, NULL };
+AISTATE zombieAGoto = { kAiStateMove, 8, -1, 1800, NULL, aiMoveForward, thinkGoto, &zombieAIdle };
+AISTATE zombieAHack = { kAiStateChase, 6, nHackClient, 80, NULL, NULL, NULL, &zombieAPonder };
+AISTATE zombieASearch = { kAiStateSearch, 8, -1, 1800, NULL, aiMoveForward, thinkSearch, &zombieAIdle };
+AISTATE zombieARecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &zombieAPonder };
+AISTATE zombieATeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &zombieAPonder };
+AISTATE zombieARecoil2 = { kAiStateRecoil, 1, -1, 360, NULL, NULL, NULL, &zombieAStand };
+AISTATE zombieAStand = { kAiStateMove, 11, nStandClient, 0, NULL, NULL, NULL, &zombieAPonder };
+AISTATE zombieEIdle = { kAiStateIdle, 12, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE zombieEUp2 = { kAiStateMove, 0, -1, 1, entryEZombie, NULL, NULL, &zombieASearch };
+AISTATE zombieEUp = { kAiStateMove, 9, -1, 180, entryEStand, NULL, NULL, &zombieEUp2 };
+AISTATE zombie2Idle = { kAiStateIdle, 0, -1, 0, entryAIdle, NULL, myThinkTarget, NULL };
+AISTATE zombie2Search = { kAiStateSearch, 8, -1, 1800, NULL, NULL, myThinkSearch, &zombie2Idle };
+AISTATE zombieSIdle = { kAiStateIdle, 10, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE zombie13AC2C = { kAiStateOther, 11, nStandClient, 0, entryEZombie, NULL, NULL, &zombieAPonder };
+
+static void HackSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type-kDudeBase];
+ int tx = pXSprite->targetX-pSprite->x;
+ int ty = pXSprite->targetY-pSprite->y;
+ int UNUSED(nDist) = approxDist(tx, ty);
+ int nAngle = getangle(tx, ty);
+ int height = (pSprite->yrepeat*pDudeInfo->eyeHeight)<<2;
+ int height2 = (pTarget->yrepeat*pDudeInfoT->eyeHeight)<<2;
+ int dz = height-height2;
+ int dx = Cos(nAngle)>>16;
+ int dy = Sin(nAngle)>>16;
+ sfxPlay3DSound(pSprite, 1101, 1, 0);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_10);
+}
+
+static void StandSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ sfxPlay3DSound(&sprite[nSprite], 1102, -1, 0);
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ sub_5F15C(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 921 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && (powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0 || powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 31) > 0))
+ {
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ aiNewState(pSprite, pXSprite, &zombieAHack);
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ pXSprite->target = -1;
+}
+
+static void thinkPonder(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &zombieASearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && (powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0 || powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 31) > 0))
+ {
+ aiNewState(pSprite, pXSprite, &zombieAGoto);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x400)
+ {
+ if (klabs(nDeltaAngle) < 85)
+ {
+ sfxPlay3DSound(pSprite, 1101, 1, 0);
+ aiNewState(pSprite, pXSprite, &zombieAHack);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &zombieAChase);
+}
+
+static void myThinkTarget(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ int nOwner = (pSprite->owner & 0x1000) ? (pSprite->owner&0xfff) : -1;
+ if (nOwner == pPlayer->at5b || pPlayer->pXSprite->health == 0 || powerupCheck(pPlayer, 13) > 0)
+ continue;
+ int x = pPlayer->pSprite->x;
+ int y = pPlayer->pSprite->y;
+ int z = pPlayer->pSprite->z;
+ int nSector = pPlayer->pSprite->sectnum;
+ int dx = x-pSprite->x;
+ int dy = y-pSprite->y;
+ int nDist = approxDist(dx, dy);
+ if (nDist > pDudeInfo->seeDist && nDist > pDudeInfo->hearDist)
+ continue;
+ if (!cansee(x, y, z, nSector, pSprite->x, pSprite->y, pSprite->z-((pDudeInfo->eyeHeight*pSprite->yrepeat)<<2), pSprite->sectnum))
+ continue;
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ if (nDist < pDudeInfo->seeDist && klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pPlayer->at5b);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else if (nDist < pDudeInfo->hearDist)
+ {
+ aiSetTarget(pXSprite, x, y, z);
+ aiActivateDude(pSprite, pXSprite);
+ }
+ else
+ continue;
+ break;
+ }
+}
+
+static void myThinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ myThinkTarget(pSprite, pXSprite);
+}
+
+static void entryEZombie(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ UNREFERENCED_PARAMETER(pXSprite);
+ pSprite->type = 203;
+ pSprite->hitag |= 1;
+}
+
+static void entryAIdle(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ UNREFERENCED_PARAMETER(pSprite);
+ pXSprite->target = -1;
+}
+
+static void entryEStand(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ sfxPlay3DSound(pSprite, 1100, -1, 0);
+ pSprite->ang = getangle(pXSprite->targetX-pSprite->x, pXSprite->targetY-pSprite->y);
+}
diff --git a/source/blood/src/aizomba.h b/source/blood/src/aizomba.h
new file mode 100644
index 000000000..443553a87
--- /dev/null
+++ b/source/blood/src/aizomba.h
@@ -0,0 +1,42 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE zombieAIdle;
+extern AISTATE zombieAChase;
+extern AISTATE zombieAPonder;
+extern AISTATE zombieAGoto;
+extern AISTATE zombieAHack;
+extern AISTATE zombieASearch;
+extern AISTATE zombieARecoil;
+extern AISTATE zombieATeslaRecoil;
+extern AISTATE zombieARecoil2;
+extern AISTATE zombieAStand;
+extern AISTATE zombieEIdle;
+extern AISTATE zombieEUp2;
+extern AISTATE zombieEUp;
+extern AISTATE zombie2Idle;
+extern AISTATE zombie2Search;
+extern AISTATE zombieSIdle;
+extern AISTATE zombie13AC2C;
diff --git a/source/blood/src/aizombf.cpp b/source/blood/src/aizombf.cpp
new file mode 100644
index 000000000..d41b1604f
--- /dev/null
+++ b/source/blood/src/aizombf.cpp
@@ -0,0 +1,230 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "aizombf.h"
+#include "blood.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "trig.h"
+
+static void HackSeqCallback(int, int);
+static void PukeSeqCallback(int, int);
+static void ThrowSeqCallback(int, int);
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite);
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite);
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite);
+
+static int nHackClient = seqRegisterClient(HackSeqCallback);
+static int nPukeClient = seqRegisterClient(PukeSeqCallback);
+static int nThrowClient = seqRegisterClient(ThrowSeqCallback);
+
+AISTATE zombieFIdle = { kAiStateIdle, 0, -1, 0, NULL, NULL, aiThinkTarget, NULL };
+AISTATE zombieFChase = { kAiStateChase, 8, -1, 0, NULL, aiMoveForward, thinkChase, NULL };
+AISTATE zombieFGoto = { kAiStateMove, 8, -1, 600, NULL, aiMoveForward, thinkGoto, &zombieFIdle };
+AISTATE zombieFDodge = { kAiStateMove, 8, -1, 0, NULL, aiMoveDodge, thinkChase, &zombieFChase };
+AISTATE zombieFHack = { kAiStateChase, 6, nHackClient, 120, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFPuke = { kAiStateChase, 9, nPukeClient, 120, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFThrow = { kAiStateChase, 6, nThrowClient, 120, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFSearch = { kAiStateSearch, 8, -1, 1800, NULL, aiMoveForward, thinkSearch, &zombieFIdle };
+AISTATE zombieFRecoil = { kAiStateRecoil, 5, -1, 0, NULL, NULL, NULL, &zombieFChase };
+AISTATE zombieFTeslaRecoil = { kAiStateRecoil, 4, -1, 0, NULL, NULL, NULL, &zombieFChase };
+
+static void HackSeqCallback(int, int nXSprite)
+{
+ if (nXSprite <= 0 || nXSprite >= kMaxXSprites)
+ return;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ if (nXSprite < 0 || nXSprite >= kMaxSprites)
+ return;
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->type != 204)
+ return;
+ spritetype *pTarget = &sprite[pXSprite->target];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat);
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type-kDudeBase];
+ int height2 = (pDudeInfoT->eyeHeight*pTarget->yrepeat);
+ actFireVector(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, height-height2, VECTOR_TYPE_11);
+}
+
+static void PukeSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pTarget = &sprite[pXSprite->target];
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ DUDEINFO *pDudeInfoT = &dudeInfo[pTarget->type-kDudeBase];
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat);
+ int height2 = (pDudeInfoT->eyeHeight*pTarget->yrepeat);
+ int tx = pXSprite->targetX-pSprite->x;
+ int ty = pXSprite->targetY-pSprite->y;
+ int UNUSED(nDist) = approxDist(tx, ty);
+ int nAngle = getangle(tx, ty);
+ int dx = Cos(nAngle)>>16;
+ int dy = Sin(nAngle)>>16;
+ sfxPlay3DSound(pSprite, 1203, 1, 0);
+ actFireMissile(pSprite, 0, -(height-height2), dx, dy, 0, 309);
+}
+
+static void ThrowSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ actFireMissile(pSprite, 0, -dudeInfo[pSprite->type-kDudeBase].eyeHeight, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, 300);
+}
+
+static void thinkSearch(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ aiChooseDirection(pSprite, pXSprite, pXSprite->goalAng);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkGoto(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ int dx = pXSprite->targetX-pSprite->x;
+ int dy = pXSprite->targetY-pSprite->y;
+ int nAngle = getangle(dx, dy);
+ int nDist = approxDist(dx, dy);
+ aiChooseDirection(pSprite, pXSprite, nAngle);
+ if (nDist < 512 && klabs(pSprite->ang - nAngle) < pDudeInfo->periphery)
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ aiThinkTarget(pSprite, pXSprite);
+}
+
+static void thinkChase(spritetype *pSprite, XSPRITE *pXSprite)
+{
+ if (pXSprite->target == -1)
+ {
+ aiNewState(pSprite, pXSprite, &zombieFGoto);
+ return;
+ }
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type - kDudeBase];
+ dassert(pXSprite->target >= 0 && pXSprite->target < kMaxSprites);
+ spritetype *pTarget = &sprite[pXSprite->target];
+ XSPRITE *pXTarget = &xsprite[pTarget->extra];
+ int dx = pTarget->x-pSprite->x;
+ int dy = pTarget->y-pSprite->y;
+ aiChooseDirection(pSprite, pXSprite, getangle(dx, dy));
+ if (pXTarget->health == 0)
+ {
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ return;
+ }
+ if (IsPlayerSprite(pTarget) && (powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 13) > 0 || powerupCheck(&gPlayer[pTarget->type-kDudePlayer1], 31) > 0))
+ {
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ return;
+ }
+ int nDist = approxDist(dx, dy);
+ if (nDist <= pDudeInfo->seeDist)
+ {
+ int nDeltaAngle = ((getangle(dx,dy)+1024-pSprite->ang)&2047)-1024;
+ int height = (pDudeInfo->eyeHeight*pSprite->yrepeat)<<2;
+ if (cansee(pTarget->x, pTarget->y, pTarget->z, pTarget->sectnum, pSprite->x, pSprite->y, pSprite->z - height, pSprite->sectnum))
+ {
+ if (klabs(nDeltaAngle) <= pDudeInfo->periphery)
+ {
+ aiSetTarget(pXSprite, pXSprite->target);
+ if (nDist < 0x1400 && nDist > 0xe00 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &zombieFThrow);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ aiNewState(pSprite, pXSprite, &zombieFThrow);
+ else
+ aiNewState(pSprite, pXSprite, &zombieFDodge);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &zombieFThrow);
+ break;
+ }
+ }
+ else if (nDist < 0x1400 && nDist > 0x600 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &zombieFPuke);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ aiNewState(pSprite, pXSprite, &zombieFPuke);
+ else
+ aiNewState(pSprite, pXSprite, &zombieFDodge);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &zombieFPuke);
+ break;
+ }
+ }
+ else if (nDist < 0x400 && klabs(nDeltaAngle) < 85)
+ {
+ int hit = HitScan(pSprite, pSprite->z, dx, dy, 0, CLIPMASK1, 0);
+ switch (hit)
+ {
+ case -1:
+ aiNewState(pSprite, pXSprite, &zombieFHack);
+ break;
+ case 3:
+ if (pSprite->type != sprite[gHitInfo.hitsprite].type)
+ aiNewState(pSprite, pXSprite, &zombieFHack);
+ else
+ aiNewState(pSprite, pXSprite, &zombieFDodge);
+ break;
+ default:
+ aiNewState(pSprite, pXSprite, &zombieFHack);
+ break;
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ aiNewState(pSprite, pXSprite, &zombieFSearch);
+ pXSprite->target = -1;
+}
diff --git a/source/blood/src/aizombf.h b/source/blood/src/aizombf.h
new file mode 100644
index 000000000..ba112cc93
--- /dev/null
+++ b/source/blood/src/aizombf.h
@@ -0,0 +1,35 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "ai.h"
+
+extern AISTATE zombieFIdle;
+extern AISTATE zombieFChase;
+extern AISTATE zombieFGoto;
+extern AISTATE zombieFDodge;
+extern AISTATE zombieFHack;
+extern AISTATE zombieFPuke;
+extern AISTATE zombieFThrow;
+extern AISTATE zombieFSearch;
+extern AISTATE zombieFRecoil;
+extern AISTATE zombieFTeslaRecoil;
diff --git a/source/blood/src/asound.cpp b/source/blood/src/asound.cpp
new file mode 100644
index 000000000..ccf86f932
--- /dev/null
+++ b/source/blood/src/asound.cpp
@@ -0,0 +1,158 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "fx_man.h"
+#include "common_game.h"
+#include "blood.h"
+#include "config.h"
+#include "db.h"
+#include "player.h"
+#include "resource.h"
+#include "sound.h"
+
+#define kMaxAmbChannel 64
+
+struct AMB_CHANNEL
+{
+ int at0;
+ int at4;
+ int at8;
+ DICTNODE *atc;
+ char *at10;
+ int at14;
+ int at18;
+};
+
+AMB_CHANNEL ambChannels[kMaxAmbChannel];
+int nAmbChannels = 0;
+
+void ambProcess(void)
+{
+ if (!SoundToggle)
+ return;
+ for (int nSprite = headspritestat[12]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->owner < 0 || pSprite->owner >= kMaxAmbChannel)
+ continue;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0 && nXSprite < kMaxXSprites)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->state)
+ {
+ int dx = pSprite->x-gMe->pSprite->x;
+ int dy = pSprite->y-gMe->pSprite->y;
+ int dz = pSprite->z-gMe->pSprite->z;
+ dx >>= 4;
+ dy >>= 4;
+ dz >>= 8;
+ int nDist = ksqrt(dx*dx+dy*dy+dz*dz);
+ int vs = mulscale16(pXSprite->data4, pXSprite->busy);
+ ambChannels[pSprite->owner].at4 += ClipRange(scale(nDist, pXSprite->data1, pXSprite->data2, vs, 0), 0, vs);
+ }
+ }
+ }
+ AMB_CHANNEL *pChannel = ambChannels;
+ for (int i = 0; i < nAmbChannels; i++, pChannel++)
+ {
+ if (pChannel->at0 > 0)
+ FX_SetPan(pChannel->at0, pChannel->at4, pChannel->at4, pChannel->at4);
+ else
+ {
+ int end = ClipLow(pChannel->at14-1, 0);
+ pChannel->at0 = FX_PlayLoopedRaw(pChannel->at10, pChannel->at14, pChannel->at10, pChannel->at10+end, sndGetRate(pChannel->at18), 0,
+ pChannel->at4, pChannel->at4, pChannel->at4, pChannel->at4, 1.f, (intptr_t)&pChannel->at0);
+ }
+ pChannel->at4 = 0;
+ }
+}
+
+void ambKillAll(void)
+{
+ AMB_CHANNEL *pChannel = ambChannels;
+ for (int i = 0; i < nAmbChannels; i++, pChannel++)
+ {
+ if (pChannel->at0 > 0)
+ {
+ FX_EndLooping(pChannel->at0);
+ FX_StopSound(pChannel->at0);
+ }
+ if (pChannel->atc)
+ {
+ gSoundRes.Unlock(pChannel->atc);
+ pChannel->atc = NULL;
+ }
+ }
+ nAmbChannels = 0;
+}
+
+void ambInit(void)
+{
+ ambKillAll();
+ memset(ambChannels, 0, sizeof(ambChannels));
+ for (int nSprite = headspritestat[12]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0 && nXSprite < kMaxXSprites)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->data1 < pXSprite->data2)
+ {
+ int i;
+ AMB_CHANNEL *pChannel = ambChannels;
+ for (i = 0; i < nAmbChannels; i++, pChannel++)
+ if (pXSprite->data3 == pChannel->at8)
+ break;
+ if (i == nAmbChannels)
+ {
+ if (i >= kMaxAmbChannel)
+ {
+ pSprite->owner = -1;
+ continue;
+ }
+ int nSFX = pXSprite->data3;
+ DICTNODE *pSFXNode = gSoundRes.Lookup(nSFX, "SFX");
+ if (!pSFXNode)
+ ThrowError("Missing sound #%d used in ambient sound generator %d\n", nSFX);
+ SFX *pSFX = (SFX*)gSoundRes.Load(pSFXNode);
+ DICTNODE *pRAWNode = gSoundRes.Lookup(pSFX->rawName, "RAW");
+ if (!pRAWNode)
+ ThrowError("Missing RAW sound \"%s\" used in ambient sound generator %d\n", pSFX->rawName, nSFX);
+ if (pRAWNode->size > 0)
+ {
+ pChannel->at14 = pRAWNode->size;
+ pChannel->at8 = nSFX;
+ pChannel->atc = pRAWNode;
+ pChannel->at14 = pRAWNode->size;
+ pChannel->at10 = (char*)gSoundRes.Lock(pRAWNode);
+ pChannel->at18 = pSFX->format;
+ nAmbChannels++;
+ }
+ }
+ pSprite->owner = i;
+ }
+ }
+ }
+}
diff --git a/source/blood/src/asound.h b/source/blood/src/asound.h
new file mode 100644
index 000000000..a442dcbda
--- /dev/null
+++ b/source/blood/src/asound.h
@@ -0,0 +1,27 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+void ambProcess(void);
+void ambKillAll(void);
+void ambInit(void);
\ No newline at end of file
diff --git a/source/blood/src/blood.cpp b/source/blood/src/blood.cpp
new file mode 100644
index 000000000..c1d0f7e60
--- /dev/null
+++ b/source/blood/src/blood.cpp
@@ -0,0 +1,2438 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "mmulti.h"
+#include "compat.h"
+#include "renderlayer.h"
+#include "fx_man.h"
+#include "common.h"
+#include "common_game.h"
+#include "gamedefs.h"
+
+#include "asound.h"
+#include "db.h"
+#include "blood.h"
+#include "choke.h"
+#include "config.h"
+#include "controls.h"
+#include "credits.h"
+#include "demo.h"
+#include "dude.h"
+#include "endgame.h"
+#include "eventq.h"
+#include "fire.h"
+#include "fx.h"
+#include "getopt.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "menu.h"
+#include "mirrors.h"
+#include "music.h"
+#include "network.h"
+#include "osdcmds.h"
+#include "replace.h"
+#include "resource.h"
+#include "qheap.h"
+#include "screen.h"
+#include "sectorfx.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "tile.h"
+#include "trig.h"
+#include "triggers.h"
+#include "view.h"
+#include "warp.h"
+#include "weapon.h"
+
+#ifdef _WIN32
+# include
+# define UPDATEINTERVAL 604800 // 1w
+# include "winbits.h"
+#else
+# ifndef GEKKO
+# include
+# endif
+#endif /* _WIN32 */
+
+const char* AppProperName = APPNAME;
+const char* AppTechnicalName = APPBASENAME;
+
+char qsprite_filler[kMaxSprites], qsector_filler[kMaxSectors];
+
+ud_setup_t gSetup;
+char SetupFilename[BMAX_PATH] = SETUPFILENAME;
+int32_t gNoSetup = 0, gCommandSetup = 0;
+
+Resource gSysRes, gGuiRes;
+
+INPUT_MODE gInputMode;
+
+unsigned int nMaxAlloc = 0x4000000;
+
+bool bCustomName = false;
+char bAddUserMap = false;
+bool bNoDemo = false;
+bool bQuickStart = true;
+bool bNoAutoLoad = false;
+
+bool bVanilla = false;
+
+int gMusicPrevLoadedEpisode = -1;
+int gMusicPrevLoadedLevel = -1;
+
+char gUserMapFilename[BMAX_PATH];
+char gPName[MAXPLAYERNAME];
+
+short BloodVersion = 0x115;
+
+int gNetPlayers;
+
+char *pUserTiles = NULL;
+char *pUserSoundRFF = NULL;
+char *pUserRFF = NULL;
+
+int gChokeCounter = 0;
+
+double g_gameUpdateTime, g_gameUpdateAndDrawTime;
+double g_gameUpdateAvgTime = 0.001;
+
+enum gametokens
+{
+ T_INCLUDE = 0,
+ T_INTERFACE = 0,
+ T_LOADGRP = 1,
+ T_MODE = 1,
+ T_CACHESIZE = 2,
+ T_ALLOW = 2,
+ T_NOAUTOLOAD,
+ T_INCLUDEDEFAULT,
+ T_MUSIC,
+ T_SOUND,
+ T_FILE,
+ //T_CUTSCENE,
+ //T_ANIMSOUNDS,
+ //T_NOFLOORPALRANGE,
+ T_ID,
+ T_MINPITCH,
+ T_MAXPITCH,
+ T_PRIORITY,
+ T_TYPE,
+ T_DISTANCE,
+ T_VOLUME,
+ T_DELAY,
+ T_RENAMEFILE,
+ T_GLOBALGAMEFLAGS,
+ T_ASPECT,
+ T_FORCEFILTER,
+ T_FORCENOFILTER,
+ T_TEXTUREFILTER,
+ T_RFFDEFINEID,
+};
+
+int blood_globalflags;
+
+void app_crashhandler(void)
+{
+ // NUKE-TODO:
+}
+
+void G_Polymer_UnInit(void)
+{
+ // NUKE-TODO:
+}
+
+void M32RunScript(const char *s)
+{
+ UNREFERENCED_PARAMETER(s);
+}
+
+static const char *_module;
+static int _line;
+
+void _SetErrorLoc(const char *pzFile, int nLine)
+{
+ _module = pzFile;
+ _line = nLine;
+}
+
+void _ThrowError(const char *pzFormat, ...)
+{
+ char buffer[256];
+ va_list args;
+ va_start(args, pzFormat);
+ vsprintf(buffer, pzFormat, args);
+ initprintf("%s(%i): %s\n", _module, _line, buffer);
+
+ char titlebuf[256];
+ Bsprintf(titlebuf, APPNAME " %s", s_buildRev);
+ wm_msgbox(titlebuf, "%s(%i): %s\n", _module, _line, buffer);
+
+ Bfflush(NULL);
+ QuitGame();
+}
+
+void __dassert(const char * pzExpr, const char * pzFile, int nLine)
+{
+ initprintf("Assertion failed: %s in file %s at line %i\n", pzExpr, pzFile, nLine);
+
+ char titlebuf[256];
+ Bsprintf(titlebuf, APPNAME " %s", s_buildRev);
+ wm_msgbox(titlebuf, "Assertion failed: %s in file %s at line %i\n", pzExpr, pzFile, nLine);
+
+ Bfflush(NULL);
+ exit(0);
+}
+
+void ShutDown(void)
+{
+ if (!in3dmode())
+ return;
+ CONFIG_WriteSetup(0);
+ netDeinitialize();
+ sndTerm();
+ sfxTerm();
+ scrUnInit();
+ CONTROL_Shutdown();
+ KB_Shutdown();
+ OSD_Cleanup();
+ // PORT_TODO: Check argument
+ if (syncstate)
+ printf("A packet was lost! (syncstate)\n");
+ for (int i = 0; i < 10; i++)
+ {
+ if (gSaveGamePic[i])
+ Resource::Free(gSaveGamePic[i]);
+ }
+ DO_FREE_AND_NULL(pUserTiles);
+ DO_FREE_AND_NULL(pUserSoundRFF);
+ DO_FREE_AND_NULL(pUserRFF);
+}
+
+void QuitGame(void)
+{
+ ShutDown();
+ exit(0);
+}
+
+int nPrecacheCount;
+
+void PrecacheDude(spritetype *pSprite)
+{
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ seqPrecacheId(pDudeInfo->seqStartID);
+ seqPrecacheId(pDudeInfo->seqStartID+5);
+ seqPrecacheId(pDudeInfo->seqStartID+1);
+ seqPrecacheId(pDudeInfo->seqStartID+2);
+ switch (pSprite->type)
+ {
+ case 201:
+ case 202:
+ case 247:
+ case 248:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ seqPrecacheId(pDudeInfo->seqStartID+8);
+ seqPrecacheId(pDudeInfo->seqStartID+9);
+ seqPrecacheId(pDudeInfo->seqStartID+13);
+ seqPrecacheId(pDudeInfo->seqStartID+14);
+ seqPrecacheId(pDudeInfo->seqStartID+15);
+ break;
+ case 204:
+ case 217:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ seqPrecacheId(pDudeInfo->seqStartID+8);
+ seqPrecacheId(pDudeInfo->seqStartID+9);
+ seqPrecacheId(pDudeInfo->seqStartID+10);
+ seqPrecacheId(pDudeInfo->seqStartID+11);
+ break;
+ case 208:
+ case 209:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ fallthrough__;
+ case 206:
+ case 207:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ seqPrecacheId(pDudeInfo->seqStartID+8);
+ seqPrecacheId(pDudeInfo->seqStartID+9);
+ break;
+ case 210:
+ case 211:
+ case 213:
+ case 214:
+ case 215:
+ case 216:
+ case 229:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ seqPrecacheId(pDudeInfo->seqStartID+8);
+ break;
+ case 227:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ fallthrough__;
+ case 212:
+ case 218:
+ case 219:
+ case 220:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ break;
+ case 249:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ break;
+ case 205:
+ seqPrecacheId(pDudeInfo->seqStartID+12);
+ seqPrecacheId(pDudeInfo->seqStartID+9);
+ fallthrough__;
+ case 244:
+ seqPrecacheId(pDudeInfo->seqStartID+10);
+ fallthrough__;
+ case 203:
+ seqPrecacheId(pDudeInfo->seqStartID+6);
+ seqPrecacheId(pDudeInfo->seqStartID+7);
+ seqPrecacheId(pDudeInfo->seqStartID+8);
+ seqPrecacheId(pDudeInfo->seqStartID+11);
+ seqPrecacheId(pDudeInfo->seqStartID+13);
+ seqPrecacheId(pDudeInfo->seqStartID+14);
+ break;
+ }
+}
+
+void PrecacheThing(spritetype *pSprite)
+{
+ switch (pSprite->type)
+ {
+ case 406:
+ case 407:
+ seqPrecacheId(12);
+ break;
+ case 410:
+ seqPrecacheId(15);
+ break;
+ case 411:
+ seqPrecacheId(21);
+ break;
+ case 412:
+ seqPrecacheId(25);
+ seqPrecacheId(26);
+ break;
+ case 413:
+ seqPrecacheId(38);
+ seqPrecacheId(40);
+ seqPrecacheId(28);
+ break;
+ case 416:
+ break;
+ default:
+ tilePreloadTile(pSprite->picnum);
+ break;
+ }
+ seqPrecacheId(3);
+ seqPrecacheId(4);
+ seqPrecacheId(5);
+ seqPrecacheId(9);
+}
+
+void PreloadTiles(void)
+{
+ int skyTile = -1;
+ memset(gotpic,0,sizeof(gotpic));
+ for (int i = 0; i < numsectors; i++)
+ {
+ tilePrecacheTile(sector[i].floorpicnum, 0);
+ tilePrecacheTile(sector[i].ceilingpicnum, 0);
+ if ((sector[i].ceilingstat&1) != 0 && skyTile == -1)
+ skyTile = sector[i].ceilingpicnum;
+ }
+ for (int i = 0; i < numwalls; i++)
+ {
+ tilePrecacheTile(wall[i].picnum, 0);
+ if (wall[i].overpicnum >= 0)
+ tilePrecacheTile(wall[i].overpicnum, 0);
+ }
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ if (sprite[i].statnum < kMaxStatus)
+ {
+ spritetype *pSprite = &sprite[i];
+ switch (pSprite->statnum)
+ {
+ case 6:
+ PrecacheDude(pSprite);
+ break;
+ case 4:
+ PrecacheThing(pSprite);
+ break;
+ default:
+ tilePrecacheTile(pSprite->picnum);
+ break;
+ }
+ }
+ }
+ if (numplayers > 1)
+ {
+ seqPrecacheId(dudeInfo[31].seqStartID+6);
+ seqPrecacheId(dudeInfo[31].seqStartID+7);
+ seqPrecacheId(dudeInfo[31].seqStartID+8);
+ seqPrecacheId(dudeInfo[31].seqStartID+9);
+ seqPrecacheId(dudeInfo[31].seqStartID+10);
+ seqPrecacheId(dudeInfo[31].seqStartID+14);
+ seqPrecacheId(dudeInfo[31].seqStartID+15);
+ seqPrecacheId(dudeInfo[31].seqStartID+12);
+ seqPrecacheId(dudeInfo[31].seqStartID+16);
+ seqPrecacheId(dudeInfo[31].seqStartID+17);
+ seqPrecacheId(dudeInfo[31].seqStartID+18);
+ }
+ if (skyTile > -1 && skyTile < kMaxTiles)
+ {
+ for (int i = 1; i < gSkyCount; i++)
+ tilePrecacheTile(skyTile+i, 0);
+ }
+ G_HandleAsync();
+}
+
+char precachehightile[2][(MAXTILES+7)>>3];
+#ifdef USE_OPENGL
+void PrecacheExtraTextureMaps(int nTile)
+{
+ // PRECACHE
+ if (useprecache && bpp > 8)
+ {
+ for (int type = 0; type < 2 && !KB_KeyPressed(sc_Space); type++)
+ {
+ if (TestBitString(precachehightile[type], nTile))
+ {
+ for (int k = 0; k < MAXPALOOKUPS - RESERVEDPALS && !KB_KeyPressed(sc_Space); k++)
+ {
+ // this is the CROSSHAIR_PAL, see screens.cpp
+ if (k == MAXPALOOKUPS - RESERVEDPALS - 1)
+ break;
+#ifdef POLYMER
+ if (videoGetRenderMode() != REND_POLYMER || !polymer_havehighpalookup(0, k))
+#endif
+ polymost_precache(nTile, k, type);
+ }
+
+#ifdef USE_GLEXT
+ if (r_detailmapping)
+ polymost_precache(nTile, DETAILPAL, type);
+
+ if (r_glowmapping)
+ polymost_precache(nTile, GLOWPAL, type);
+#endif
+#ifdef POLYMER
+ if (videoGetRenderMode() == REND_POLYMER)
+ {
+ if (pr_specularmapping)
+ polymost_precache(nTile, SPECULARPAL, type);
+
+ if (pr_normalmapping)
+ polymost_precache(nTile, NORMALPAL, type);
+ }
+#endif
+ }
+ }
+ }
+}
+#endif
+
+void PreloadCache(void)
+{
+ char tempbuf[128];
+ if (gDemo.at1)
+ return;
+ if (MusicRestartsOnLoadToggle)
+ sndTryPlaySpecialMusic(MUS_LOADING);
+ gSoundRes.PrecacheSounds();
+ PreloadTiles();
+ int clock = totalclock;
+ int cnt = 0;
+ int percentDisplayed = -1;
+
+ for (int i=0; i (kTicRate>>2))
+ {
+ int const percentComplete = min(100, tabledivide32_noinline(100 * cnt, nPrecacheCount));
+
+ // this just prevents the loading screen percentage bar from making large jumps
+ while (percentDisplayed < percentComplete)
+ {
+ Bsprintf(tempbuf, "Loaded %d%% (%d/%d textures)\n", percentDisplayed, cnt, nPrecacheCount);
+ viewLoadingScreenUpdate(tempbuf, percentDisplayed);
+ timerUpdate();
+
+ if (totalclock - clock >= 1)
+ {
+ clock = totalclock;
+ percentDisplayed++;
+ }
+ }
+
+ clock = totalclock;
+ }
+ }
+ }
+ memset(gotpic,0,sizeof(gotpic));
+}
+
+void EndLevel(void)
+{
+ gViewPos = VIEWPOS_0;
+ gGameMessageMgr.Clear();
+ sndKillAllSounds();
+ sfxKillAllSounds();
+ ambKillAll();
+ seqKillAll();
+}
+
+PLAYER gPlayerTemp[kMaxPlayers];
+int gHealthTemp[kMaxPlayers];
+
+vec3_t startpos;
+int16_t startang, startsectnum;
+
+void StartLevel(GAMEOPTIONS *gameOptions)
+{
+ EndLevel();
+ gStartNewGame = 0;
+ ready2send = 0;
+ gMusicPrevLoadedEpisode = gGameOptions.nEpisode;
+ gMusicPrevLoadedLevel = gGameOptions.nLevel;
+ if (gDemo.at0 && gGameStarted)
+ gDemo.Close();
+ netWaitForEveryone(0);
+ if (gGameOptions.nGameType == 0)
+ {
+ if (!(gGameOptions.uGameFlags&1))
+ levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
+ if (gEpisodeInfo[gGameOptions.nEpisode].cutALevel == gGameOptions.nLevel
+ && gEpisodeInfo[gGameOptions.nEpisode].at8f08)
+ gGameOptions.uGameFlags |= 4;
+ if ((gGameOptions.uGameFlags&4) && gDemo.at1 == 0)
+ levelPlayIntroScene(gGameOptions.nEpisode);
+ }
+ else if (gGameOptions.nGameType > 0 && !(gGameOptions.uGameFlags&1))
+ {
+ gGameOptions.nEpisode = gPacketStartGame.episodeId;
+ gGameOptions.nLevel = gPacketStartGame.levelId;
+ gGameOptions.nGameType = gPacketStartGame.gameType;
+ gGameOptions.nDifficulty = gPacketStartGame.difficulty;
+ gGameOptions.nMonsterSettings = gPacketStartGame.monsterSettings;
+ gGameOptions.nWeaponSettings = gPacketStartGame.weaponSettings;
+ gGameOptions.nItemSettings = gPacketStartGame.itemSettings;
+ gGameOptions.nRespawnSettings = gPacketStartGame.respawnSettings;
+ if (gPacketStartGame.userMap)
+ levelAddUserMap(gPacketStartGame.userMapName);
+ else
+ levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
+ }
+ if (gameOptions->uGameFlags&1)
+ {
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ memcpy(&gPlayerTemp[i],&gPlayer[i],sizeof(PLAYER));
+ gHealthTemp[i] = xsprite[gPlayer[i].pSprite->extra].health;
+ }
+ }
+ bVanilla = gDemo.at1 && gDemo.m_bLegacy;
+ //blooddemohack = 1;//bVanilla;
+ memset(xsprite,0,sizeof(xsprite));
+ memset(sprite,0,kMaxSprites*sizeof(spritetype));
+ drawLoadingScreen();
+ dbLoadMap(gameOptions->zLevelName,(int*)&startpos.x,(int*)&startpos.y,(int*)&startpos.z,&startang,&startsectnum,(unsigned int*)&gameOptions->uMapCRC);
+ wsrand(gameOptions->uMapCRC);
+ gKillMgr.Clear();
+ gSecretMgr.Clear();
+ gLevelTime = 0;
+ automapping = 1;
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ spritetype *pSprite = &sprite[i];
+ if (pSprite->statnum < kMaxStatus && pSprite->extra > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if ((pXSprite->lSkill & (1 << gameOptions->nDifficulty)) || (pXSprite->lS && gameOptions->nGameType == 0)
+ || (pXSprite->lB && gameOptions->nGameType == 2) || (pXSprite->lT && gameOptions->nGameType == 3)
+ || (pXSprite->lC && gameOptions->nGameType == 1)) {
+
+ DeleteSprite(i);
+ continue;
+ }
+
+ if (sprite[i].lotag == kGDXDudeTargetChanger)
+ InsertSpriteStat(i, kStatGDXDudeTargetChanger);
+ }
+ }
+ scrLoadPLUs();
+ startpos.z = getflorzofslope(startsectnum,startpos.x,startpos.y);
+ for (int i = 0; i < kMaxPlayers; i++)
+ {
+ gStartZone[i].x = startpos.x;
+ gStartZone[i].y = startpos.y;
+ gStartZone[i].z = startpos.z;
+ gStartZone[i].sectnum = startsectnum;
+ gStartZone[i].ang = startang;
+
+ // By NoOne: Create spawn zones for players in teams mode.
+ if (i <= kMaxPlayers / 2) {
+ gStartZoneTeam1[i].x = startpos.x;
+ gStartZoneTeam1[i].y = startpos.y;
+ gStartZoneTeam1[i].z = startpos.z;
+ gStartZoneTeam1[i].sectnum = startsectnum;
+ gStartZoneTeam1[i].ang = startang;
+
+ gStartZoneTeam2[i].x = startpos.x;
+ gStartZoneTeam2[i].y = startpos.y;
+ gStartZoneTeam2[i].z = startpos.z;
+ gStartZoneTeam2[i].sectnum = startsectnum;
+ gStartZoneTeam2[i].ang = startang;
+ }
+ }
+ InitSectorFX();
+ warpInit();
+ actInit();
+ evInit();
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ if (!(gameOptions->uGameFlags&1))
+ {
+ if (numplayers == 1)
+ {
+ gProfile[i].skill = gSkill;
+ gProfile[i].nAutoAim = gAutoAim;
+ gProfile[i].nWeaponSwitch = gWeaponSwitch;
+ }
+ playerInit(i,0);
+ }
+ playerStart(i);
+ }
+ if (gameOptions->uGameFlags&1)
+ {
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ PLAYER *pPlayer = &gPlayer[i];
+ pPlayer->pXSprite->health &= 0xf000;
+ pPlayer->pXSprite->health |= gHealthTemp[i];
+ pPlayer->at26 = gPlayerTemp[i].at26;
+ pPlayer->atbd = gPlayerTemp[i].atbd;
+ pPlayer->atc3 = gPlayerTemp[i].atc3;
+ pPlayer->atc7 = gPlayerTemp[i].atc7;
+ pPlayer->at2a = gPlayerTemp[i].at2a;
+ pPlayer->at1b1 = gPlayerTemp[i].at1b1;
+ pPlayer->atbf = gPlayerTemp[i].atbf;
+ pPlayer->atbe = gPlayerTemp[i].atbe;
+ }
+ }
+ gameOptions->uGameFlags &= ~3;
+ scrSetDac();
+ PreloadCache();
+ InitMirrors();
+ gFrameClock = 0;
+ trInit();
+ if (!bVanilla && !gMe->packInfo[1].at0) // if diving suit is not active, turn off reverb sound effect
+ sfxSetReverb(0);
+ ambInit();
+ sub_79760();
+ gCacheMiss = 0;
+ gFrame = 0;
+ gChokeCounter = 0;
+ if (!gDemo.at1)
+ gGameMenuMgr.Deactivate();
+ levelTryPlayMusicOrNothing(gGameOptions.nEpisode, gGameOptions.nLevel);
+ // viewSetMessage("");
+ viewSetErrorMessage("");
+ viewResizeView(gViewSize);
+ if (gGameOptions.nGameType == 3)
+ gGameMessageMgr.SetCoordinates(gViewX0S+1,gViewY0S+15);
+ netWaitForEveryone(0);
+ gGameClock = 0;
+ gPaused = 0;
+ gGameStarted = 1;
+ ready2send = 1;
+}
+
+void StartNetworkLevel(void)
+{
+ if (gDemo.at0)
+ gDemo.Close();
+ if (!(gGameOptions.uGameFlags&1))
+ {
+ gGameOptions.nEpisode = gPacketStartGame.episodeId;
+ gGameOptions.nLevel = gPacketStartGame.levelId;
+ gGameOptions.nGameType = gPacketStartGame.gameType;
+ gGameOptions.nDifficulty = gPacketStartGame.difficulty;
+ gGameOptions.nMonsterSettings = gPacketStartGame.monsterSettings;
+ gGameOptions.nWeaponSettings = gPacketStartGame.weaponSettings;
+ gGameOptions.nItemSettings = gPacketStartGame.itemSettings;
+ gGameOptions.nRespawnSettings = gPacketStartGame.respawnSettings;
+ if (gPacketStartGame.userMap)
+ levelAddUserMap(gPacketStartGame.userMapName);
+ else
+ levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
+ }
+ StartLevel(&gGameOptions);
+}
+
+void LocalKeys(void)
+{
+ char alt = keystatus[sc_LeftAlt] | keystatus[sc_RightAlt];
+ char ctrl = keystatus[sc_LeftControl] | keystatus[sc_RightControl];
+ char shift = keystatus[sc_LeftShift] | keystatus[sc_RightShift];
+ if (BUTTON(gamefunc_See_Chase_View) && !alt && !shift)
+ {
+ CONTROL_ClearButton(gamefunc_See_Chase_View);
+ if (gViewPos > VIEWPOS_0)
+ gViewPos = VIEWPOS_0;
+ else
+ gViewPos = VIEWPOS_1;
+ }
+ if (BUTTON(gamefunc_See_Coop_View))
+ {
+ CONTROL_ClearButton(gamefunc_See_Coop_View);
+ if (gGameOptions.nGameType == 1)
+ {
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ gView = &gPlayer[gViewIndex];
+ }
+ else if (gGameOptions.nGameType == 3)
+ {
+ int oldViewIndex = gViewIndex;
+ do
+ {
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ if (oldViewIndex == gViewIndex || gMe->at2ea == gPlayer[gViewIndex].at2ea)
+ break;
+ } while (oldViewIndex != gViewIndex);
+ gView = &gPlayer[gViewIndex];
+ }
+ }
+ char key;
+ if ((key = keyGetScan()) != 0)
+ {
+ if ((alt || shift) && gGameOptions.nGameType > 0 && key >= 0x3b && key <= 0x44)
+ {
+ char fk = key - 0x3b;
+ if (alt)
+ {
+ netBroadcastTaunt(myconnectindex, fk);
+ }
+ else
+ {
+ gPlayerMsg.Set(CommbatMacro[fk]);
+ gPlayerMsg.Send();
+ }
+ keyFlushScans();
+ keystatus[key] = 0;
+ CONTROL_ClearButton(41);
+ return;
+ }
+ switch (key)
+ {
+ case 0x53:
+ case 0xd3:
+ if (ctrl && alt)
+ {
+ gQuitGame = 1;
+ return;
+ }
+ break;
+ case 0x01:
+ keyFlushScans();
+ if (gGameStarted && gPlayer[myconnectindex].pXSprite->health != 0)
+ {
+ if (!gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Push(&menuMainWithSave,-1);
+ }
+ else
+ {
+ if (!gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Push(&menuMain,-1);
+ }
+ return;
+ case 0x3b:
+ keyFlushScans();
+ if (gGameOptions.nGameType == 0)
+ gGameMenuMgr.Push(&menuOrder,-1);
+ break;
+ case 0x3c:
+ keyFlushScans();
+ if (!gGameMenuMgr.m_bActive && gGameOptions.nGameType == 0)
+ gGameMenuMgr.Push(&menuSaveGame,-1);
+ break;
+ case 0x3d:
+ keyFlushScans();
+ if (!gGameMenuMgr.m_bActive && gGameOptions.nGameType == 0)
+ gGameMenuMgr.Push(&menuLoadGame,-1);
+ break;
+ case 0x3e:
+ keyFlushScans();
+ if (!gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Push(&menuOptionsSound,-1);
+ return;
+ case 0x3f:
+ keyFlushScans();
+ if (!gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Push(&menuOptions,-1);
+ return;
+ case 0x40:
+ keyFlushScans();
+ if (gGameStarted && !gGameMenuMgr.m_bActive && gPlayer[myconnectindex].pXSprite->health != 0)
+ {
+ if (gQuickSaveSlot != -1)
+ {
+ QuickSaveGame();
+ return;
+ }
+ gGameMenuMgr.Push(&menuSaveGame,-1);
+ }
+ break;
+ case 0x42:
+ keyFlushScans();
+ gGameMenuMgr.Push(&menuOptions,-1);
+ break;
+ case 0x43:
+ keyFlushScans();
+ if (!gGameMenuMgr.m_bActive)
+ {
+ if (gQuickLoadSlot != -1)
+ {
+ QuickLoadGame();
+ return;
+ }
+ if (gQuickLoadSlot == -1 && gQuickSaveSlot != -1)
+ {
+ gQuickLoadSlot = gQuickSaveSlot;
+ QuickLoadGame();
+ return;
+ }
+ gGameMenuMgr.Push(&menuLoadGame,-1);
+ }
+ break;
+ case 0x44:
+ keyFlushScans();
+ if (!gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Push(&menuQuit,-1);
+ break;
+ case 0x57:
+ break;
+ case 0x58:
+ videoCaptureScreen("blud0000.tga", 0);
+ break;
+ }
+ }
+}
+
+bool gRestartGame = false;
+
+void ProcessFrame(void)
+{
+ char buffer[128];
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ gPlayer[i].atc.buttonFlags = gFifoInput[gNetFifoTail&255][i].buttonFlags;
+ gPlayer[i].atc.keyFlags.word |= gFifoInput[gNetFifoTail&255][i].keyFlags.word;
+ gPlayer[i].atc.useFlags.byte |= gFifoInput[gNetFifoTail&255][i].useFlags.byte;
+ if (gFifoInput[gNetFifoTail&255][i].newWeapon)
+ gPlayer[i].atc.newWeapon = gFifoInput[gNetFifoTail&255][i].newWeapon;
+ gPlayer[i].atc.forward = gFifoInput[gNetFifoTail&255][i].forward;
+ gPlayer[i].atc.q16turn = gFifoInput[gNetFifoTail&255][i].q16turn;
+ gPlayer[i].atc.strafe = gFifoInput[gNetFifoTail&255][i].strafe;
+ gPlayer[i].atc.q16mlook = gFifoInput[gNetFifoTail&255][i].q16mlook;
+ }
+ gNetFifoTail++;
+ if (!(gFrame&((gSyncRate<<3)-1)))
+ {
+ CalcGameChecksum();
+ memcpy(gCheckFifo[gCheckHead[myconnectindex]&255][myconnectindex], gChecksum, sizeof(gChecksum));
+ gCheckHead[myconnectindex]++;
+ }
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ if (gPlayer[i].atc.keyFlags.quit)
+ {
+ gPlayer[i].atc.keyFlags.quit = 0;
+ netBroadcastPlayerLogoff(i);
+ if (i == myconnectindex)
+ {
+ // netBroadcastMyLogoff(gQuitRequest == 2);
+ gQuitGame = true;
+ gRestartGame = gQuitRequest == 2;
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ }
+ }
+ if (gPlayer[i].atc.keyFlags.restart)
+ {
+ gPlayer[i].atc.keyFlags.restart = 0;
+ levelRestart();
+ return;
+ }
+ if (gPlayer[i].atc.keyFlags.pause)
+ {
+ gPlayer[i].atc.keyFlags.pause = 0;
+ gPaused = !gPaused;
+ if (gPaused && gGameOptions.nGameType > 0 && numplayers > 1)
+ {
+ sprintf(buffer,"%s paused the game",gProfile[i].name);
+ viewSetMessage(buffer);
+ }
+ }
+ }
+ viewClearInterpolations();
+ if (!gDemo.at1)
+ {
+ if (gPaused || gEndGameMgr.at0 || (gGameOptions.nGameType == 0 && gGameMenuMgr.m_bActive))
+ return;
+ if (gDemo.at0)
+ gDemo.Write(gFifoInput[(gNetFifoTail-1)&255]);
+ }
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ viewBackupView(i);
+ playerProcess(&gPlayer[i]);
+ }
+ trProcessBusy();
+ evProcess(gFrameClock);
+ seqProcess(4);
+ DoSectorPanning();
+ actProcessSprites();
+ actPostProcess();
+ viewCorrectPrediction();
+ sndProcess();
+ ambProcess();
+ viewUpdateDelirium();
+ viewUpdateShake();
+ sfxUpdate3DSounds();
+ if (gMe->at376 == 1)
+ {
+#define CHOKERATE 8
+#define TICRATE 30
+ gChokeCounter += CHOKERATE;
+ while (gChokeCounter >= TICRATE)
+ {
+ gChoke.at1c(gMe);
+ gChokeCounter -= TICRATE;
+ }
+ }
+ gLevelTime++;
+ gFrame++;
+ gFrameClock += 4;
+ if ((gGameOptions.uGameFlags&1) != 0 && !gStartNewGame)
+ {
+ ready2send = 0;
+ if (gNetPlayers > 1 && gNetMode == NETWORK_SERVER && gPacketMode == PACKETMODE_1 && myconnectindex == connecthead)
+ {
+ while (gNetFifoMasterTail < gNetFifoTail)
+ {
+ G_HandleAsync();
+ netMasterUpdate();
+ }
+ }
+ if (gDemo.at0)
+ gDemo.Close();
+ sndFadeSong(4000);
+ seqKillAll();
+ if (gGameOptions.uGameFlags&2)
+ {
+ if (gGameOptions.nGameType == 0)
+ {
+ if (gGameOptions.uGameFlags&8)
+ levelPlayEndScene(gGameOptions.nEpisode);
+ gGameMenuMgr.Deactivate();
+ gGameMenuMgr.Push(&menuCredits,-1);
+ }
+ gGameOptions.uGameFlags &= ~3;
+ gRestartGame = 1;
+ gQuitGame = 1;
+ }
+ else
+ {
+ gEndGameMgr.Setup();
+ viewResizeView(gViewSize);
+ }
+ }
+}
+
+SWITCH switches[] = {
+ { "?", 0, 0 },
+ { "help", 0, 0 },
+ { "broadcast", 1, 0 },
+ { "map", 2, 1 },
+ { "masterslave", 3, 0 },
+ //{ "net", 4, 1 },
+ { "nodudes", 5, 1 },
+ { "playback", 6, 1 },
+ { "record", 7, 1 },
+ { "robust", 8, 0 },
+ { "setupfile", 9, 1 },
+ { "skill", 10, 1 },
+ //{ "nocd", 11, 0 },
+ //{ "8250", 12, 0 },
+ { "ini", 13, 1 },
+ { "noaim", 14, 0 },
+ { "f", 15, 1 },
+ { "control", 16, 1 },
+ { "vector", 17, 1 },
+ { "quick", 18, 0 },
+ //{ "getopt", 19, 1 },
+ //{ "auto", 20, 1 },
+ { "pname", 21, 1 },
+ { "noresend", 22, 0 },
+ { "silentaim", 23, 0 },
+ { "nodemo", 25, 0 },
+ { "art", 26, 1 },
+ { "snd", 27, 1 },
+ { "rff", 28, 1 },
+ { "maxalloc", 29, 1 },
+ { "server", 30, 1 },
+ { "client", 31, 1 },
+ { "noautoload", 32, 0 },
+ { "usecwd", 33, 0 },
+ { "cachesize", 34, 1 },
+ { "g", 35, 1 },
+ { "grp", 35, 1 },
+ { "game_dir", 36, 1 },
+ { "cfg", 9, 1 },
+ { "setup", 37, 0 },
+ { "nosetup", 38, 0 },
+ { "port", 39, 1 },
+ { "h", 40, 1 },
+ { "mh", 41, 1 },
+ { "j", 42, 1 },
+ { NULL, 0, 0 }
+};
+
+void PrintHelp(void)
+{
+ char tempbuf[128];
+ static char const s[] = "Usage: " APPBASENAME " [files] [options]\n"
+ "Example: " APPBASENAME " -usecwd -cfg myconfig.cfg -map nukeland.map\n\n"
+ "Files can be of type [grp|zip|map|def]\n"
+ "\n"
+ "-art [file.art]\tSpecify an art base file name\n"
+ "-cachesize #\tSet cache size in kB\n"
+ "-cfg [file.cfg]\tUse an alternate configuration file\n"
+ "-client [host]\tConnect to a multiplayer game\n"
+ "-game_dir [dir]\tSpecify game data directory\n"
+ "-g [file.grp]\tLoad additional game data\n"
+ "-h [file.def]\tLoad an alternate definitions file\n"
+ "-ini [file.ini]\tSpecify an INI file name (default is blood.ini)\n"
+ "-j [dir]\t\tAdd a directory to " APPNAME "'s search list\n"
+ "-map [file.map]\tLoad an external map file\n"
+ "-mh [file.def]\tInclude an additional definitions module\n"
+ "-noautoload\tDisable loading from autoload directory\n"
+ "-nodemo\t\tNo Demos\n"
+ "-nodudes\tNo monsters\n"
+ "-playback\tPlay back a demo\n"
+ "-pname\t\tOverride player name setting from config file\n"
+ "-record\t\tRecord demo\n"
+ "-rff\t\tSpecify an RFF file for Blood game resources\n"
+ "-server [players]\tStart a multiplayer server\n"
+#ifdef STARTUP_SETUP_WINDOW
+ "-setup/nosetup\tEnable or disable startup window\n"
+#endif
+ "-skill\t\tSet player handicap; Range:0..4; Default:2; (NOT difficulty level.)\n"
+ "-snd\t\tSpecify an RFF Sound file name\n"
+ "-usecwd\t\tRead data and configuration from current directory\n"
+ ;
+#ifdef WM_MSGBOX_WINDOW
+ Bsnprintf(tempbuf, sizeof(tempbuf), APPNAME " %s", s_buildRev);
+ wm_msgbox(tempbuf, s);
+#else
+ initprintf("%s\n", s);
+#endif
+#if 0
+ puts("Blood Command-line Options:");
+ // NUKE-TODO:
+ puts("-? This help");
+ //puts("-8250 Enforce obsolete UART I/O");
+ //puts("-auto Automatic Network start. Implies -quick");
+ //puts("-getopt Use network game options from file. Implies -auto");
+ puts("-broadcast Set network to broadcast packet mode");
+ puts("-masterslave Set network to master/slave packet mode");
+ //puts("-net Net mode game");
+ //puts("-noaim Disable auto-aiming");
+ //puts("-nocd Disable CD audio");
+ puts("-nodudes No monsters");
+ puts("-nodemo No Demos");
+ puts("-robust Robust network sync checking");
+ puts("-skill Set player handicap; Range:0..4; Default:2; (NOT difficulty level.)");
+ puts("-quick Skip Intro screens and get right to the game");
+ puts("-pname Override player name setting from config file");
+ puts("-map Specify a user map");
+ puts("-playback Play back a demo");
+ puts("-record Record a demo");
+ puts("-art Specify an art base file name");
+ puts("-snd Specify an RFF Sound file name");
+ puts("-RFF Specify an RFF file for Blood game resources");
+ puts("-ini Specify an INI file name (default is blood.ini)");
+#endif
+ exit(0);
+}
+
+void ParseOptions(void)
+{
+ int option;
+ while ((option = GetOptions(switches)) != -1)
+ {
+ switch (option)
+ {
+ case -3:
+ ThrowError("Invalid argument: %s", OptFull);
+ fallthrough__;
+ case 29:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ nMaxAlloc = atoi(OptArgv[0]);
+ if (!nMaxAlloc)
+ nMaxAlloc = 0x2000000;
+ break;
+ case 0:
+ PrintHelp();
+ break;
+ //case 19:
+ // byte_148eec = 1;
+ //case 20:
+ // if (OptArgc < 1)
+ // ThrowError("Missing argument");
+ // strncpy(byte_148ef0, OptArgv[0], 13);
+ // byte_148ef0[12] = 0;
+ // bQuickStart = 1;
+ // byte_148eeb = 1;
+ // if (gGameOptions.gameType == 0)
+ // gGameOptions.gameType = 2;
+ // break;
+ case 25:
+ bNoDemo = 1;
+ break;
+ case 18:
+ bQuickStart = 1;
+ break;
+ //case 12:
+ // EightyTwoFifty = 1;
+ // break;
+ case 1:
+ gPacketMode = PACKETMODE_2;
+ break;
+ case 21:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ strcpy(gPName, OptArgv[0]);
+ bCustomName = 1;
+ break;
+ case 2:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ strcpy(gUserMapFilename, OptArgv[0]);
+ bAddUserMap = 1;
+ bNoDemo = 1;
+ break;
+ case 3:
+ if (gSyncRate == 1)
+ gPacketMode = PACKETMODE_2;
+ else
+ gPacketMode = PACKETMODE_1;
+ break;
+ case 4:
+ //if (OptArgc < 1)
+ // ThrowError("Missing argument");
+ //if (gGameOptions.nGameType == 0)
+ // gGameOptions.nGameType = 2;
+ break;
+ case 30:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ gNetPlayers = ClipRange(atoi(OptArgv[0]), 1, kMaxPlayers);
+ gNetMode = NETWORK_SERVER;
+ break;
+ case 31:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ gNetMode = NETWORK_CLIENT;
+ strncpy(gNetAddress, OptArgv[0], sizeof(gNetAddress)-1);
+ break;
+ case 14:
+ gAutoAim = 0;
+ break;
+ case 22:
+ bNoResend = 0;
+ break;
+ case 23:
+ bSilentAim = 1;
+ break;
+ case 5:
+ gGameOptions.nMonsterSettings = 0;
+ break;
+ case 6:
+ if (OptArgc < 1)
+ gDemo.SetupPlayback(NULL);
+ else
+ gDemo.SetupPlayback(OptArgv[0]);
+ break;
+ case 7:
+ if (OptArgc < 1)
+ gDemo.Create(NULL);
+ else
+ gDemo.Create(OptArgv[0]);
+ break;
+ case 8:
+ gRobust = 1;
+ break;
+ case 13:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ levelOverrideINI(OptArgv[0]);
+ bNoDemo = 1;
+ break;
+ case 26:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ pUserTiles = (char*)malloc(strlen(OptArgv[0])+1);
+ if (!pUserTiles)
+ return;
+ strcpy(pUserTiles, OptArgv[0]);
+ break;
+ case 27:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ pUserSoundRFF = (char*)malloc(strlen(OptArgv[0])+1);
+ if (!pUserSoundRFF)
+ return;
+ strcpy(pUserSoundRFF, OptArgv[0]);
+ break;
+ case 28:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ pUserRFF = (char*)malloc(strlen(OptArgv[0])+1);
+ if (!pUserRFF)
+ return;
+ strcpy(pUserRFF, OptArgv[0]);
+ break;
+ case 9:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ strcpy(SetupFilename, OptArgv[0]);
+ break;
+ case 10:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ gSkill = strtoul(OptArgv[0], NULL, 0);
+ if (gSkill < 0)
+ gSkill = 0;
+ else if (gSkill > 4)
+ gSkill = 4;
+ break;
+ case 15:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ gSyncRate = ClipRange(strtoul(OptArgv[0], NULL, 0), 1, 4);
+ if (gPacketMode == PACKETMODE_1)
+ gSyncRate = 1;
+ else if (gPacketMode == PACKETMODE_3)
+ gSyncRate = 1;
+ break;
+ case -2:
+ {
+ const char *k = strrchr(OptFull, '.');
+ if (k)
+ {
+ if (!Bstrcasecmp(k, ".map"))
+ {
+ strcpy(gUserMapFilename, OptFull);
+ bAddUserMap = 1;
+ bNoDemo = 1;
+ }
+ else if (!Bstrcasecmp(k, ".grp") || !Bstrcasecmp(k, ".zip") || !Bstrcasecmp(k, ".pk3") || !Bstrcasecmp(k, ".pk4"))
+ {
+ G_AddGroup(OptFull);
+ }
+ else if (!Bstrcasecmp(k, ".def"))
+ {
+ clearDefNamePtr();
+ g_defNamePtr = dup_filename(OptFull);
+ initprintf("Using DEF file \"%s\".\n", g_defNamePtr);
+ continue;
+ }
+ }
+ else
+ {
+ strcpy(gUserMapFilename, OptFull);
+ bAddUserMap = 1;
+ bNoDemo = 1;
+ }
+ break;
+ }
+ case 11:
+ //bNoCDAudio = 1;
+ break;
+ case 32:
+ initprintf("Autoload disabled\n");
+ bNoAutoLoad = true;
+ break;
+ case 33:
+ g_useCwd = true;
+ break;
+ case 34:
+ {
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ uint32_t j = strtoul(OptArgv[0], NULL, 0);
+ MAXCACHE1DSIZE = j<<10;
+ initprintf("Cache size: %dkB\n", j);
+ break;
+ }
+ case 35:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ G_AddGroup(OptArgv[0]);
+ break;
+ case 36:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ Bstrncpyz(g_modDir, OptArgv[0], sizeof(g_modDir));
+ G_AddPath(OptArgv[0]);
+ break;
+ case 37:
+ gCommandSetup = true;
+ break;
+ case 38:
+ gNoSetup = true;
+ gCommandSetup = false;
+ break;
+ case 39:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ gNetPort = strtoul(OptArgv[0], NULL, 0);
+ break;
+ case 40:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ G_AddDef(OptArgv[0]);
+ break;
+ case 41:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ G_AddDefModule(OptArgv[0]);
+ break;
+ case 42:
+ if (OptArgc < 1)
+ ThrowError("Missing argument");
+ G_AddPath(OptArgv[0]);
+ break;
+ }
+ }
+#if 0
+ if (bAddUserMap)
+ {
+ char zNode[BMAX_PATH];
+ char zDir[BMAX_PATH];
+ char zFName[BMAX_PATH];
+ _splitpath(gUserMapFilename, zNode, zDir, zFName, NULL);
+ strcpy(g_modDir, zNode);
+ strcat(g_modDir, zDir);
+ strcpy(gUserMapFilename, zFName);
+ }
+#endif
+}
+
+void ClockStrobe()
+{
+ gGameClock++;
+}
+
+#if defined(_WIN32) && defined(DEBUGGINGAIDS)
+// See FILENAME_CASE_CHECK in cache1d.c
+static int32_t check_filename_casing(void)
+{
+ return 1;
+}
+#endif
+
+int app_main(int argc, char const * const * argv)
+{
+ char buffer[BMAX_PATH];
+ margc = argc;
+ margv = argv;
+#ifdef _WIN32
+ if (!G_CheckCmdSwitch(argc, argv, "-noinstancechecking") && win_checkinstance())
+ {
+ if (!wm_ynbox(APPNAME, "Another Build game is currently running. "
+ "Do you wish to continue starting this copy?"))
+ return 3;
+ }
+
+ backgroundidle = 0;
+
+ G_ExtPreInit(argc, argv);
+
+#ifdef DEBUGGINGAIDS
+ extern int32_t (*check_filename_casing_fn)(void);
+ check_filename_casing_fn = check_filename_casing;
+#endif
+#endif
+
+ OSD_SetLogFile(APPBASENAME ".log");
+
+ OSD_SetFunctions(NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ GAME_clearbackground,
+ BGetTime,
+ GAME_onshowosd);
+
+ wm_setapptitle(APPNAME);
+
+ initprintf(APPNAME " %s\n", s_buildRev);
+ PrintBuildInfo();
+
+ memcpy(&gGameOptions, &gSingleGameOptions, sizeof(GAMEOPTIONS));
+ ParseOptions();
+ G_ExtInit();
+
+ if (!g_useCwd)
+ G_AddSearchPaths();
+
+ // used with binds for fast function lookup
+ hash_init(&h_gamefuncs);
+ for (bssize_t i=NUMGAMEFUNCTIONS-1; i>=0; i--)
+ {
+ if (gamefunctions[i][0] == '\0')
+ continue;
+
+ char *str = Bstrtolower(Xstrdup(gamefunctions[i]));
+ hash_add(&h_gamefuncs,gamefunctions[i],i,0);
+ hash_add(&h_gamefuncs,str,i,0);
+ Bfree(str);
+ }
+
+#ifdef STARTUP_SETUP_WINDOW
+ int const readSetup =
+#endif
+ CONFIG_ReadSetup();
+ if (bCustomName)
+ strcpy(szPlayerName, gPName);
+
+ if (enginePreInit())
+ {
+ wm_msgbox("Build Engine Initialization Error",
+ "There was a problem initializing the Build engine: %s", engineerrstr);
+ ERRprintf("app_main: There was a problem initializing the Build engine: %s\n", engineerrstr);
+ Bexit(2);
+ }
+
+ if (Bstrcmp(SetupFilename, SETUPFILENAME))
+ initprintf("Using config file \"%s\".\n", SetupFilename);
+
+ ScanINIFiles();
+
+#ifdef STARTUP_SETUP_WINDOW
+ if (readSetup < 0 || (!gNoSetup && (configversion != BYTEVERSION || gSetup.forcesetup)) || gCommandSetup)
+ {
+ if (quitevent || !startwin_run())
+ {
+ engineUnInit();
+ Bexit(0);
+ }
+ }
+#endif
+
+ G_LoadGroups(!bNoAutoLoad && !gSetup.noautoload);
+
+ //if (!g_useCwd)
+ // G_CleanupSearchPaths();
+
+ initprintf("Initializing OSD...\n");
+
+ //Bsprintf(tempbuf, HEAD2 " %s", s_buildRev);
+ OSD_SetVersion("Blood", 10, 0);
+ OSD_SetParameters(0, 0, 0, 12, 2, 12, OSD_ERROR, OSDTEXT_RED, gamefunctions[gamefunc_Show_Console][0] == '\0' ? OSD_PROTECTED : 0);
+ registerosdcommands();
+
+ char *const setupFileName = Xstrdup(SetupFilename);
+ char *const p = strtok(setupFileName, ".");
+
+ if (!p || !Bstrcmp(SetupFilename, SETUPFILENAME))
+ Bsprintf(buffer, "settings.cfg");
+ else
+ Bsprintf(buffer, "%s_settings.cfg", p);
+
+ Bfree(setupFileName);
+
+ OSD_Exec(buffer);
+ OSD_Exec("autoexec.cfg");
+
+ // Not neccessary ?
+ // CONFIG_SetDefaultKeys(keydefaults, true);
+
+ system_getcvars();
+
+ Resource::heap = new QHeap(nMaxAlloc);
+ gSysRes.Init(pUserRFF ? pUserRFF : "BLOOD.RFF");
+ gGuiRes.Init("GUI.RFF");
+ gSoundRes.Init(pUserSoundRFF ? pUserSoundRFF : "SOUNDS.RFF");
+
+
+ { // Replace
+ void qinitspritelists();
+ int32_t qinsertsprite(int16_t nSector, int16_t nStat);
+ int32_t qdeletesprite(int16_t nSprite);
+ int32_t qchangespritesect(int16_t nSprite, int16_t nSector);
+ int32_t qchangespritestat(int16_t nSprite, int16_t nStatus);
+ animateoffs_replace = qanimateoffs;
+ paletteLoadFromDisk_replace = qloadpalette;
+ getpalookup_replace = qgetpalookup;
+ initspritelists_replace = qinitspritelists;
+ insertsprite_replace = qinsertsprite;
+ deletesprite_replace = qdeletesprite;
+ changespritesect_replace = qchangespritesect;
+ changespritestat_replace = qchangespritestat;
+ loadvoxel_replace = qloadvoxel;
+ bloodhack = true;
+ }
+
+ initprintf("Initializing Build 3D engine\n");
+ scrInit();
+
+ initprintf("Loading tiles\n");
+ if (pUserTiles)
+ {
+ strcpy(buffer,pUserTiles);
+ strcat(buffer,"%03i.ART");
+ if (!tileInit(0,buffer))
+ ThrowError("User specified ART files not found");
+ }
+ else
+ {
+ if (!tileInit(0,NULL))
+ ThrowError("TILES###.ART files not found");
+ }
+
+ LoadExtraArts();
+
+ levelLoadDefaults();
+ const char *defsfile = G_DefFile();
+ uint32_t stime = timerGetTicks();
+ if (!loaddefinitionsfile(defsfile))
+ {
+ uint32_t etime = timerGetTicks();
+ initprintf("Definitions file \"%s\" loaded in %d ms.\n", defsfile, etime-stime);
+ }
+ loaddefinitions_game(defsfile, FALSE);
+ powerupInit();
+ initprintf("Loading cosine table\n");
+ trigInit(gSysRes);
+ initprintf("Initializing view subsystem\n");
+ viewInit();
+ initprintf("Initializing dynamic fire\n");
+ FireInit();
+ initprintf("Initializing weapon animations\n");
+ WeaponInit();
+ LoadSaveSetup();
+ LoadSavedInfo();
+ gDemo.LoadDemoInfo();
+ initprintf("There are %d demo(s) in the loop\n", gDemo.at59ef);
+ initprintf("Loading control setup\n");
+ ctrlInit();
+ timerInit(120);
+ timerSetCallback(ClockStrobe);
+ // PORT-TODO: CD audio init
+
+ initprintf("Initializing network users\n");
+ netInitialize(true);
+ scrSetGameMode(gSetup.fullscreen, gSetup.xdim, gSetup.ydim, gSetup.bpp);
+ scrSetGamma(gGamma);
+ viewResizeView(gViewSize);
+ initprintf("Initializing sound system\n");
+ sndInit();
+ sfxInit();
+ gChoke.sub_83ff0(518, sub_84230);
+ if (bAddUserMap)
+ {
+ levelAddUserMap(gUserMapFilename);
+ gStartNewGame = 1;
+ }
+ SetupMenus();
+ videoSetViewableArea(0, 0, xdim - 1, ydim - 1);
+ if (!bQuickStart)
+ credLogosDos();
+ scrSetDac();
+RESTART:
+ sub_79760();
+ gViewIndex = myconnectindex;
+ gMe = gView = &gPlayer[myconnectindex];
+ netBroadcastPlayerInfo(myconnectindex);
+ initprintf("Waiting for network players!\n");
+ netWaitForEveryone(0);
+ if (gRestartGame)
+ {
+ // Network error
+ gQuitGame = false;
+ gRestartGame = false;
+ netDeinitialize();
+ netResetToSinglePlayer();
+ goto RESTART;
+ }
+ UpdateNetworkMenus();
+ if (!gDemo.at0 && gDemo.at59ef > 0 && gGameOptions.nGameType == 0 && !bNoDemo)
+ gDemo.SetupPlayback(NULL);
+ viewSetCrosshairColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);
+ gQuitGame = 0;
+ gRestartGame = 0;
+ if (gGameOptions.nGameType > 0)
+ {
+ KB_ClearKeysDown();
+ KB_FlushKeyboardQueue();
+ keyFlushScans();
+ }
+ else if (gDemo.at1 && !bAddUserMap && !bNoDemo)
+ gDemo.Playback();
+ if (gDemo.at59ef > 0)
+ gGameMenuMgr.Deactivate();
+ if (!bAddUserMap && !gGameStarted)
+ gGameMenuMgr.Push(&menuMain, -1);
+ ready2send = 1;
+ while (!gQuitGame)
+ {
+ if (handleevents() && quitevent)
+ {
+ KB_KeyDown[sc_Escape] = 1;
+ quitevent = 0;
+ }
+ netUpdate();
+ MUSIC_Update();
+ CONTROL_BindsEnabled = gInputMode == INPUT_MODE_0;
+ switch (gInputMode)
+ {
+ case INPUT_MODE_1:
+ if (gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Process();
+ break;
+ case INPUT_MODE_0:
+ LocalKeys();
+ break;
+ default:
+ break;
+ }
+ if (gQuitGame)
+ continue;
+
+ OSD_DispatchQueued();
+
+ bool bDraw;
+ if (gGameStarted)
+ {
+ char gameUpdate = false;
+ double const gameUpdateStartTime = timerGetHiTicks();
+ G_HandleAsync();
+ while (gPredictTail < gNetFifoHead[myconnectindex] && !gPaused)
+ {
+ viewUpdatePrediction(&gFifoInput[gPredictTail&255][myconnectindex]);
+ }
+ if (numplayers == 1)
+ gBufferJitter = 0;
+ while (gGameClock >= gNetFifoClock && ready2send)
+ {
+ netGetInput();
+ gNetFifoClock += 4;
+ while (gNetFifoHead[myconnectindex]-gNetFifoTail > gBufferJitter && !gStartNewGame && !gQuitGame)
+ {
+ int i;
+ for (i = connecthead; i >= 0; i = connectpoint2[i])
+ if (gNetFifoHead[i] == gNetFifoTail)
+ break;
+ if (i >= 0)
+ break;
+ faketimerhandler();
+ ProcessFrame();
+ timerUpdate();
+ gameUpdate = true;
+ }
+ timerUpdate();
+ }
+ if (gameUpdate)
+ {
+ g_gameUpdateTime = timerGetHiTicks() - gameUpdateStartTime;
+ if (g_gameUpdateAvgTime < 0.f)
+ g_gameUpdateAvgTime = g_gameUpdateTime;
+ g_gameUpdateAvgTime = ((GAMEUPDATEAVGTIMENUMSAMPLES-1.f)*g_gameUpdateAvgTime+g_gameUpdateTime)/((float) GAMEUPDATEAVGTIMENUMSAMPLES);
+ }
+ bDraw = viewFPSLimit() != 0;
+ if (gQuitRequest && gQuitGame)
+ videoClearScreen(0);
+ else
+ {
+ netCheckSync();
+ if (bDraw)
+ {
+ viewDrawScreen();
+ g_gameUpdateAndDrawTime = timerGetHiTicks() - gameUpdateStartTime;
+ }
+ }
+ }
+ else
+ {
+ bDraw = viewFPSLimit() != 0;
+ if (bDraw)
+ {
+ videoClearScreen(0);
+ rotatesprite(160<<16,100<<16,65536,0,2518,0,0,0x4a,0,0,xdim-1,ydim-1);
+ }
+ G_HandleAsync();
+ if (gQuitRequest && !gQuitGame)
+ netBroadcastMyLogoff(gQuitRequest == 2);
+ }
+ if (bDraw)
+ {
+ switch (gInputMode)
+ {
+ case INPUT_MODE_1:
+ if (gGameMenuMgr.m_bActive)
+ gGameMenuMgr.Draw();
+ break;
+ case INPUT_MODE_2:
+ gPlayerMsg.ProcessKeys();
+ gPlayerMsg.Draw();
+ break;
+ case INPUT_MODE_3:
+ gEndGameMgr.ProcessKeys();
+ gEndGameMgr.Draw();
+ break;
+ default:
+ break;
+ }
+ }
+ //scrNextPage();
+ if (TestBitString(gotpic, 2342))
+ {
+ FireProcess();
+ ClearBitString(gotpic, 2342);
+ }
+ //if (byte_148e29 && gStartNewGame)
+ //{
+ // gStartNewGame = 0;
+ // gQuitGame = 1;
+ //}
+ if (gStartNewGame)
+ StartLevel(&gGameOptions);
+ }
+ ready2send = 0;
+ if (gDemo.at0)
+ gDemo.Close();
+ if (gRestartGame)
+ {
+ UpdateDacs(0, true);
+ sndStopSong();
+ FX_StopAllSounds();
+ gQuitGame = 0;
+ gQuitRequest = 0;
+ gRestartGame = 0;
+ gGameStarted = 0;
+ levelSetupOptions(0,0);
+ while (gGameMenuMgr.m_bActive)
+ {
+ gGameMenuMgr.Process();
+ G_HandleAsync();
+ if (viewFPSLimit())
+ {
+ videoClearScreen(0);
+ gGameMenuMgr.Draw();
+ }
+ }
+ if (gGameOptions.nGameType != 0)
+ {
+ if (!gDemo.at0 && gDemo.at59ef > 0 && gGameOptions.nGameType == 0 && !bNoDemo)
+ gDemo.NextDemo();
+ videoSetViewableArea(0,0,xdim-1,ydim-1);
+ if (!bQuickStart)
+ credLogosDos();
+ scrSetDac();
+ }
+ goto RESTART;
+ }
+ ShutDown();
+
+ return 0;
+}
+
+static int32_t S_DefineAudioIfSupported(char *fn, const char *name)
+{
+#if !defined HAVE_FLAC || !defined HAVE_VORBIS
+ const char *extension = Bstrrchr(name, '.');
+# if !defined HAVE_FLAC
+ if (extension && !Bstrcasecmp(extension, ".flac"))
+ return -2;
+# endif
+# if !defined HAVE_VORBIS
+ if (extension && !Bstrcasecmp(extension, ".ogg"))
+ return -2;
+# endif
+#endif
+ Bstrncpy(fn, name, BMAX_PATH);
+ return 0;
+}
+
+// Returns:
+// 0: all OK
+// -1: ID declaration was invalid:
+static int32_t S_DefineMusic(const char *ID, const char *name)
+{
+ int32_t sel = MUS_FIRST_SPECIAL;
+
+ Bassert(ID != NULL);
+
+ if (!Bstrcmp(ID,"intro"))
+ {
+ sel = MUS_INTRO;
+ }
+ else if (!Bstrcmp(ID,"loading"))
+ {
+ sel = MUS_LOADING;
+ }
+ else
+ {
+ sel = levelGetMusicIdx(ID);
+ if (sel < 0)
+ return -1;
+ }
+
+ int nEpisode = sel/kMaxLevels;
+ int nLevel = sel%kMaxLevels;
+ return S_DefineAudioIfSupported(gEpisodeInfo[nEpisode].at28[nLevel].atd0, name);
+}
+
+static int parsedefinitions_game(scriptfile *, int);
+
+static void parsedefinitions_game_include(const char *fileName, scriptfile *pScript, const char *cmdtokptr, int const firstPass)
+{
+ scriptfile *included = scriptfile_fromfile(fileName);
+
+ if (!included)
+ {
+ if (!Bstrcasecmp(cmdtokptr,"null") || pScript == NULL) // this is a bit overboard to prevent unused parameter warnings
+ {
+ // initprintf("Warning: Failed including %s as module\n", fn);
+ }
+/*
+ else
+ {
+ initprintf("Warning: Failed including %s on line %s:%d\n",
+ fn, script->filename,scriptfile_getlinum(script,cmdtokptr));
+ }
+*/
+ }
+ else
+ {
+ parsedefinitions_game(included, firstPass);
+ scriptfile_close(included);
+ }
+}
+
+#if 0
+static void parsedefinitions_game_animsounds(scriptfile *pScript, const char * blockEnd, char const * fileName, dukeanim_t * animPtr)
+{
+ Bfree(animPtr->sounds);
+
+ size_t numPairs = 0, allocSize = 4;
+
+ animPtr->sounds = (animsound_t *)Xmalloc(allocSize * sizeof(animsound_t));
+ animPtr->numsounds = 0;
+
+ int defError = 1;
+ uint16_t lastFrameNum = 1;
+
+ while (pScript->textptr < blockEnd)
+ {
+ int32_t frameNum;
+ int32_t soundNum;
+
+ // HACK: we've reached the end of the list
+ // (hack because it relies on knowledge of
+ // how scriptfile_* preprocesses the text)
+ if (blockEnd - pScript->textptr == 1)
+ break;
+
+ // would produce error when it encounters the closing '}'
+ // without the above hack
+ if (scriptfile_getnumber(pScript, &frameNum))
+ break;
+
+ defError = 1;
+
+ if (scriptfile_getsymbol(pScript, &soundNum))
+ break;
+
+ // frame numbers start at 1 for us
+ if (frameNum <= 0)
+ {
+ initprintf("Error: frame number must be greater zero on line %s:%d\n", pScript->filename,
+ scriptfile_getlinum(pScript, pScript->ltextptr));
+ break;
+ }
+
+ if (frameNum < lastFrameNum)
+ {
+ initprintf("Error: frame numbers must be in (not necessarily strictly)"
+ " ascending order (line %s:%d)\n",
+ pScript->filename, scriptfile_getlinum(pScript, pScript->ltextptr));
+ break;
+ }
+
+ lastFrameNum = frameNum;
+
+ if ((unsigned)soundNum >= MAXSOUNDS && soundNum != -1)
+ {
+ initprintf("Error: sound number #%d invalid on line %s:%d\n", soundNum, pScript->filename,
+ scriptfile_getlinum(pScript, pScript->ltextptr));
+ break;
+ }
+
+ if (numPairs >= allocSize)
+ {
+ allocSize *= 2;
+ animPtr->sounds = (animsound_t *)Xrealloc(animPtr->sounds, allocSize * sizeof(animsound_t));
+ }
+
+ defError = 0;
+
+ animsound_t & sound = animPtr->sounds[numPairs];
+ sound.frame = frameNum;
+ sound.sound = soundNum;
+
+ ++numPairs;
+ }
+
+ if (!defError)
+ {
+ animPtr->numsounds = numPairs;
+ // initprintf("Defined sound sequence for hi-anim \"%s\" with %d frame/sound pairs\n",
+ // hardcoded_anim_tokens[animnum].text, numpairs);
+ }
+ else
+ {
+ DO_FREE_AND_NULL(animPtr->sounds);
+ initprintf("Failed defining sound sequence for anim \"%s\".\n", fileName);
+ }
+}
+
+#endif
+
+static int parsedefinitions_game(scriptfile *pScript, int firstPass)
+{
+ int token;
+ char *pToken;
+
+ static const tokenlist tokens[] =
+ {
+ { "include", T_INCLUDE },
+ { "#include", T_INCLUDE },
+ { "includedefault", T_INCLUDEDEFAULT },
+ { "#includedefault", T_INCLUDEDEFAULT },
+ { "loadgrp", T_LOADGRP },
+ { "cachesize", T_CACHESIZE },
+ { "noautoload", T_NOAUTOLOAD },
+ { "music", T_MUSIC },
+ { "sound", T_SOUND },
+ //{ "cutscene", T_CUTSCENE },
+ //{ "animsounds", T_ANIMSOUNDS },
+ { "renamefile", T_RENAMEFILE },
+ { "globalgameflags", T_GLOBALGAMEFLAGS },
+ { "rffdefineid", T_RFFDEFINEID },
+ };
+
+ static const tokenlist soundTokens[] =
+ {
+ { "id", T_ID },
+ { "file", T_FILE },
+ { "minpitch", T_MINPITCH },
+ { "maxpitch", T_MAXPITCH },
+ { "priority", T_PRIORITY },
+ { "type", T_TYPE },
+ { "distance", T_DISTANCE },
+ { "volume", T_VOLUME },
+ };
+
+#if 0
+ static const tokenlist animTokens [] =
+ {
+ { "delay", T_DELAY },
+ { "aspect", T_ASPECT },
+ { "sounds", T_SOUND },
+ { "forcefilter", T_FORCEFILTER },
+ { "forcenofilter", T_FORCENOFILTER },
+ { "texturefilter", T_TEXTUREFILTER },
+ };
+#endif
+
+ do
+ {
+ token = getatoken(pScript, tokens, ARRAY_SIZE(tokens));
+ pToken = pScript->ltextptr;
+
+ switch (token)
+ {
+ case T_LOADGRP:
+ {
+ char *fileName;
+
+ pathsearchmode = 1;
+ if (!scriptfile_getstring(pScript,&fileName) && firstPass)
+ {
+ if (initgroupfile(fileName) == -1)
+ initprintf("Could not find file \"%s\".\n", fileName);
+ else
+ {
+ initprintf("Using file \"%s\" as game data.\n", fileName);
+ if (!bNoAutoLoad && !gSetup.noautoload)
+ G_DoAutoload(fileName);
+ }
+ }
+
+ pathsearchmode = 0;
+ }
+ break;
+ case T_CACHESIZE:
+ {
+ int32_t cacheSize;
+
+ if (scriptfile_getnumber(pScript, &cacheSize) || !firstPass)
+ break;
+
+ if (cacheSize > 0)
+ MAXCACHE1DSIZE = cacheSize << 10;
+ }
+ break;
+ case T_INCLUDE:
+ {
+ char *fileName;
+
+ if (!scriptfile_getstring(pScript, &fileName))
+ parsedefinitions_game_include(fileName, pScript, pToken, firstPass);
+
+ break;
+ }
+ case T_INCLUDEDEFAULT:
+ {
+ parsedefinitions_game_include(G_DefaultDefFile(), pScript, pToken, firstPass);
+ break;
+ }
+ case T_NOAUTOLOAD:
+ if (firstPass)
+ bNoAutoLoad = true;
+ break;
+ case T_MUSIC:
+ {
+ char *tokenPtr = pScript->ltextptr;
+ char *musicID = NULL;
+ char *fileName = NULL;
+ char *musicEnd;
+
+ if (scriptfile_getbraces(pScript, &musicEnd))
+ break;
+
+ while (pScript->textptr < musicEnd)
+ {
+ switch (getatoken(pScript, soundTokens, ARRAY_SIZE(soundTokens)))
+ {
+ case T_ID: scriptfile_getstring(pScript, &musicID); break;
+ case T_FILE: scriptfile_getstring(pScript, &fileName); break;
+ }
+ }
+
+ if (!firstPass)
+ {
+ if (musicID==NULL)
+ {
+ initprintf("Error: missing ID for music definition near line %s:%d\n",
+ pScript->filename, scriptfile_getlinum(pScript,tokenPtr));
+ break;
+ }
+
+ if (fileName == NULL || check_file_exist(fileName))
+ break;
+
+ if (S_DefineMusic(musicID, fileName) == -1)
+ initprintf("Error: invalid music ID on line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript, tokenPtr));
+ }
+ }
+ break;
+
+ case T_RFFDEFINEID:
+ {
+ char *resName = NULL;
+ char *resType = NULL;
+ char *rffName = NULL;
+ int resID;
+
+ if (scriptfile_getstring(pScript, &resName))
+ break;
+
+ if (scriptfile_getstring(pScript, &resType))
+ break;
+
+ if (scriptfile_getnumber(pScript, &resID))
+ break;
+
+ if (scriptfile_getstring(pScript, &rffName))
+ break;
+
+ if (!firstPass)
+ {
+ if (!Bstrcasecmp(rffName, "SYSTEM"))
+ gSysRes.AddExternalResource(resName, resType, resID);
+ else if (!Bstrcasecmp(rffName, "SOUND"))
+ gSoundRes.AddExternalResource(resName, resType, resID);
+ }
+ }
+ break;
+
+#if 0
+ case T_CUTSCENE:
+ {
+ char *fileName = NULL;
+
+ scriptfile_getstring(pScript, &fileName);
+
+ char *animEnd;
+
+ if (scriptfile_getbraces(pScript, &animEnd))
+ break;
+
+ if (!firstPass)
+ {
+ dukeanim_t *animPtr = Anim_Find(fileName);
+
+ if (!animPtr)
+ {
+ animPtr = Anim_Create(fileName);
+ animPtr->framedelay = 10;
+ animPtr->frameflags = 0;
+ }
+
+ int32_t temp;
+
+ while (pScript->textptr < animEnd)
+ {
+ switch (getatoken(pScript, animTokens, ARRAY_SIZE(animTokens)))
+ {
+ case T_DELAY:
+ scriptfile_getnumber(pScript, &temp);
+ animPtr->framedelay = temp;
+ break;
+ case T_ASPECT:
+ {
+ double dtemp, dtemp2;
+ scriptfile_getdouble(pScript, &dtemp);
+ scriptfile_getdouble(pScript, &dtemp2);
+ animPtr->frameaspect1 = dtemp;
+ animPtr->frameaspect2 = dtemp2;
+ break;
+ }
+ case T_SOUND:
+ {
+ char *animSoundsEnd = NULL;
+ if (scriptfile_getbraces(pScript, &animSoundsEnd))
+ break;
+ parsedefinitions_game_animsounds(pScript, animSoundsEnd, fileName, animPtr);
+ break;
+ }
+ case T_FORCEFILTER:
+ animPtr->frameflags |= CUTSCENE_FORCEFILTER;
+ break;
+ case T_FORCENOFILTER:
+ animPtr->frameflags |= CUTSCENE_FORCENOFILTER;
+ break;
+ case T_TEXTUREFILTER:
+ animPtr->frameflags |= CUTSCENE_TEXTUREFILTER;
+ break;
+ }
+ }
+ }
+ else
+ pScript->textptr = animEnd;
+ }
+ break;
+ case T_ANIMSOUNDS:
+ {
+ char *tokenPtr = pScript->ltextptr;
+ char *fileName = NULL;
+
+ scriptfile_getstring(pScript, &fileName);
+ if (!fileName)
+ break;
+
+ char *animSoundsEnd = NULL;
+
+ if (scriptfile_getbraces(pScript, &animSoundsEnd))
+ break;
+
+ if (firstPass)
+ {
+ pScript->textptr = animSoundsEnd;
+ break;
+ }
+
+ dukeanim_t *animPtr = Anim_Find(fileName);
+
+ if (!animPtr)
+ {
+ initprintf("Error: expected animation filename on line %s:%d\n",
+ pScript->filename, scriptfile_getlinum(pScript, tokenPtr));
+ break;
+ }
+
+ parsedefinitions_game_animsounds(pScript, animSoundsEnd, fileName, animPtr);
+ }
+ break;
+ case T_SOUND:
+ {
+ char *tokenPtr = pScript->ltextptr;
+ char *fileName = NULL;
+ char *musicEnd;
+
+ double volume = 1.0;
+
+ int32_t soundNum = -1;
+ int32_t maxpitch = 0;
+ int32_t minpitch = 0;
+ int32_t priority = 0;
+ int32_t type = 0;
+ int32_t distance = 0;
+
+ if (scriptfile_getbraces(pScript, &musicEnd))
+ break;
+
+ while (pScript->textptr < musicEnd)
+ {
+ switch (getatoken(pScript, soundTokens, ARRAY_SIZE(soundTokens)))
+ {
+ case T_ID: scriptfile_getsymbol(pScript, &soundNum); break;
+ case T_FILE: scriptfile_getstring(pScript, &fileName); break;
+ case T_MINPITCH: scriptfile_getsymbol(pScript, &minpitch); break;
+ case T_MAXPITCH: scriptfile_getsymbol(pScript, &maxpitch); break;
+ case T_PRIORITY: scriptfile_getsymbol(pScript, &priority); break;
+ case T_TYPE: scriptfile_getsymbol(pScript, &type); break;
+ case T_DISTANCE: scriptfile_getsymbol(pScript, &distance); break;
+ case T_VOLUME: scriptfile_getdouble(pScript, &volume); break;
+ }
+ }
+
+ if (!firstPass)
+ {
+ if (soundNum==-1)
+ {
+ initprintf("Error: missing ID for sound definition near line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript,tokenPtr));
+ break;
+ }
+
+ if (fileName == NULL || check_file_exist(fileName))
+ break;
+
+ // maybe I should have just packed this into a sound_t and passed a reference...
+ if (S_DefineSound(soundNum, fileName, minpitch, maxpitch, priority, type, distance, volume) == -1)
+ initprintf("Error: invalid sound ID on line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript,tokenPtr));
+ }
+ }
+ break;
+#endif
+ case T_GLOBALGAMEFLAGS: scriptfile_getnumber(pScript, &blood_globalflags); break;
+ case T_EOF: return 0;
+ default: break;
+ }
+ }
+ while (1);
+
+ return 0;
+}
+
+int loaddefinitions_game(const char *fileName, int32_t firstPass)
+{
+ scriptfile *pScript = scriptfile_fromfile(fileName);
+
+ if (pScript)
+ parsedefinitions_game(pScript, firstPass);
+
+ for (char const * m : g_defModules)
+ parsedefinitions_game_include(m, NULL, "null", firstPass);
+
+ if (pScript)
+ scriptfile_close(pScript);
+
+ scriptfile_clearsymbols();
+
+ return 0;
+}
+
+INICHAIN *pINIChain;
+INICHAIN const*pINISelected;
+int nINICount = 0;
+
+const char *pzCrypticArts[] = {
+ "CPART07.AR_", "CPART15.AR_"
+};
+
+INIDESCRIPTION gINIDescription[] = {
+ { "BLOOD: One Unit Whole Blood", "BLOOD.INI", NULL, 0 },
+ { "Cryptic passage", "CRYPTIC.INI", pzCrypticArts, ARRAY_SSIZE(pzCrypticArts) },
+};
+
+bool AddINIFile(const char *pzFile, bool bForce = false)
+{
+ char *pzFN;
+ struct Bstat st;
+ static INICHAIN *pINIIter = NULL;
+ if (!bForce)
+ {
+ if (findfrompath(pzFile, &pzFN)) return false; // failed to resolve the filename
+ if (Bstat(pzFN, &st))
+ {
+ Bfree(pzFN);
+ return false;
+ } // failed to stat the file
+ Bfree(pzFN);
+ IniFile *pTempIni = new IniFile(pzFile);
+ if (!pTempIni->FindSection("Episode1"))
+ {
+ delete pTempIni;
+ return false;
+ }
+ delete pTempIni;
+ }
+ if (!pINIChain)
+ pINIIter = pINIChain = new INICHAIN;
+ else
+ pINIIter = pINIIter->pNext = new INICHAIN;
+ pINIIter->pNext = NULL;
+ pINIIter->pDescription = NULL;
+ Bstrncpy(pINIIter->zName, pzFile, BMAX_PATH);
+ for (int i = 0; i < ARRAY_SSIZE(gINIDescription); i++)
+ {
+ if (!Bstrncasecmp(pINIIter->zName, gINIDescription[i].pzFilename, BMAX_PATH))
+ {
+ pINIIter->pDescription = &gINIDescription[i];
+ break;
+ }
+ }
+ return true;
+}
+
+void ScanINIFiles(void)
+{
+ nINICount = 0;
+ CACHE1D_FIND_REC *pINIList = klistpath("/", "*.ini", CACHE1D_FIND_FILE);
+ pINIChain = NULL;
+
+ if (bINIOverride || !pINIList)
+ {
+ AddINIFile(BloodIniFile, true);
+ }
+
+ for (auto pIter = pINIList; pIter; pIter = pIter->next)
+ {
+ AddINIFile(pIter->name);
+ }
+ klistfree(pINIList);
+ pINISelected = pINIChain;
+ for (auto pIter = pINIChain; pIter; pIter = pIter->pNext)
+ {
+ if (!Bstrncasecmp(BloodIniFile, pIter->zName, BMAX_PATH))
+ {
+ pINISelected = pIter;
+ break;
+ }
+ }
+}
+
+bool LoadArtFile(const char *pzFile)
+{
+ int hFile = kopen4loadfrommod(pzFile, 0);
+ if (hFile == -1)
+ {
+ initprintf("Can't open extra art file:\"%s\"\n", pzFile);
+ return false;
+ }
+ artheader_t artheader;
+ int nStatus = artReadHeader(hFile, pzFile, &artheader);
+ if (nStatus != 0)
+ {
+ kclose(hFile);
+ initprintf("Error reading extra art file:\"%s\"\n", pzFile);
+ return false;
+ }
+ for (int i = artheader.tilestart; i <= artheader.tileend; i++)
+ tileDelete(i);
+ artReadManifest(hFile, &artheader);
+ artPreloadFile(hFile, &artheader);
+ for (int i = artheader.tilestart; i <= artheader.tileend; i++)
+ tileUpdatePicSiz(i);
+ kclose(hFile);
+ return true;
+}
+
+void LoadExtraArts(void)
+{
+ if (!pINISelected->pDescription)
+ return;
+ for (int i = 0; i < pINISelected->pDescription->nArts; i++)
+ {
+ LoadArtFile(pINISelected->pDescription->pzArts[i]);
+ }
+}
+
+bool DemoRecordStatus(void) {
+ return gDemo.at0;
+}
+
+bool VanillaMode() {
+ return gDemo.m_bLegacy && gDemo.at1;
+}
+
+bool fileExistsRFF(int id, const char *ext) {
+ return gSysRes.Lookup(id, ext);
+}
\ No newline at end of file
diff --git a/source/blood/src/blood.h b/source/blood/src/blood.h
new file mode 100644
index 000000000..c310bcd7d
--- /dev/null
+++ b/source/blood/src/blood.h
@@ -0,0 +1,249 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "levels.h"
+#include "resource.h"
+
+#define TILTBUFFER 4078
+
+#define kExplodeMax 8
+#define kDudeBase 200
+#define kDudePlayer1 231
+#define kDudePlayer8 238
+#define kDudeMax 260
+#define kMissileBase 300
+#define kMissileMax 318
+#define kThingBase 400
+#define kThingMax 436
+
+#define kMaxPowerUps 51
+
+#define kStatRespawn 8
+#define kStatMarker 10
+#define kStatGDXDudeTargetChanger 20
+#define kStatFree 1024
+
+#define kLensSize 80
+#define kViewEffectMax 19
+
+#define kNoTile -1
+
+
+// defined by NoOne:
+// -------------------------------
+#define kMaxPAL 5
+
+#define kWeaponItemBase 40
+#define kItemMax 151
+
+// marker sprite types
+#define kMarkerSPStart 1
+#define kMarkerMPStart 2
+#define kMarkerOff 3
+#define kMarkerOn 4
+#define kMarkerAxis 5
+#define kMarkerLowLink 6
+#define kMarkerUpLink 7
+#define kMarkerWarpDest 8
+#define kMarkerUpWater 9
+#define kMarkerLowWater 10
+#define kMarkerUpStack 11
+#define kMarkerLowStack 12
+#define kMarkerUpGoo 13
+#define kMarkerLowGoo 14
+#define kMarkerPath 15
+
+// sprite cstat
+#define kSprBlock 0x0001
+#define kSprTrans 0x0002
+#define kSprFlipX 0x0004
+#define kSprFlipY 0x0008
+#define kSprFace 0x0000
+#define kSprWall 0x0010
+#define kSprFloor 0x0020
+#define kSprSpin 0x0030
+#define kSprRMask 0x0030
+#define kSprOneSided 0x0040
+#define kSprOriginAlign 0x0080
+#define kSprHitscan 0x0100
+#define kSprTransR 0x0200
+#define kSprPushable 0x1000
+#define kSprMoveMask 0x6000
+#define kSprMoveNone 0x0000
+#define kSprMoveForward 0x2000
+#define kSprMoveFloor 0x2000
+#define kSprMoveReverse 0x4000
+#define kSprMoveCeiling 0x4000
+#define kSprInvisible 0x8000
+
+// sprite attributes
+#define kHitagMovePhys 0x0001 // affected by movement physics
+#define kHitagGravityPhys 0x0002 // affected by gravity
+#define kHitagFalling 0x0004 // currently in z-motion
+#define kHitagAutoAim 0x0008
+#define kHitagRespawn 0x0010
+#define kHitagFree 0x0020
+#define kHitagSmoke 0x0100
+#define kHitagExtBit 0x8000 // NoOne's extension bit(Note: it's bit 0 in editor!)
+
+// sector types
+#define kSecBase 600
+#define kSecZMotion kSectorBase
+#define kSecZSprite 602
+#define kSecWarp 603
+#define kSecTeleport 604
+#define kSecPath 612
+#define kSecRotateStep 613
+#define kSecSlideMarked 614
+#define kSecRotateMarked 615
+#define kSecSlide 616
+#define kSecRotate 617
+#define kSecDamage 618
+#define kSecCounter 619
+#define kSecMax 620
+
+// switch types
+#define kSwitchBase 20
+#define kSwitchToggle 20
+#define kSwitchOneWay 21
+#define kSwitchCombo 22
+#define kSwitchPadlock 23
+#define kSwitchMax 24
+
+// projectile types
+#define kProjectileEctoSkull 307
+
+// custom level end
+#define kGDXChannelEndLevelCustom 6
+
+// GDX types
+#define kGDXTypeBase 24
+#define kGDXCustomDudeSpawn 24
+#define kGDXRandomTX 25
+#define kGDXSequentialTX 26
+#define kGDXSeqSpawner 27
+#define kGDXObjPropertiesChanger 28
+#define kGDXObjPicnumChanger 29
+#define kGDXObjSizeChanger 31
+#define kGDXDudeTargetChanger 33
+#define kGDXSectorFXChanger 34
+#define kGDXObjDataChanger 35
+#define kGDXSpriteDamager 36
+#define kGDXObjDataAccumulator 37
+#define kGDXEffectSpawner 38
+#define kGDXWindGenerator 39
+
+#define kGDXThingTNTProx 433 // detects only players
+#define kGDXThingThrowableRock 434 // does small damage if hits target
+#define kGDXThingCustomDudeLifeLeech 435 // the same as normal, except it aims in specified target
+#define kGDXDudeUniversalCultist 254
+#define kGDXGenDudeBurning 255
+
+#define kGDXItemMapLevel 150 // once picked up, draws whole minimap
+
+// ai state types
+#define kAiStateOther -1
+#define kAiStateIdle 0
+#define kAiStateGenIdle 1
+#define kAiStateMove 2
+#define kAiStateSearch 3
+#define kAiStateChase 4
+#define kAiStateRecoil 5
+
+
+#define kAng5 28
+#define kAng15 85
+#define kAng30 170
+#define kAng45 256
+#define kAng60 341
+#define kAng90 512
+#define kAng120 682
+#define kAng180 1024
+#define kAng360 2048
+
+
+// -------------------------------
+
+struct INIDESCRIPTION {
+ const char *pzName;
+ const char *pzFilename;
+ const char **pzArts;
+ int nArts;
+};
+
+struct INICHAIN {
+ INICHAIN *pNext;
+ char zName[BMAX_PATH];
+ INIDESCRIPTION *pDescription;
+};
+
+extern INICHAIN *pINIChain;
+extern INICHAIN const*pINISelected;
+
+typedef struct {
+ int32_t usejoystick;
+ int32_t usemouse;
+ int32_t fullscreen;
+ int32_t xdim;
+ int32_t ydim;
+ int32_t bpp;
+ int32_t forcesetup;
+ int32_t noautoload;
+} ud_setup_t;
+
+enum INPUT_MODE {
+ INPUT_MODE_0 = 0,
+ INPUT_MODE_1,
+ INPUT_MODE_2,
+ INPUT_MODE_3,
+};
+
+extern Resource gSysRes, gGuiRes;
+extern INPUT_MODE gInputMode;
+extern ud_setup_t gSetup;
+extern char SetupFilename[BMAX_PATH];
+extern int32_t gNoSetup;
+extern short BloodVersion;
+extern int gNetPlayers;
+extern bool gRestartGame;
+#define GAMEUPDATEAVGTIMENUMSAMPLES 100
+extern double g_gameUpdateTime, g_gameUpdateAndDrawTime;
+extern double g_gameUpdateAvgTime;
+extern int blood_globalflags;
+extern bool bVanilla;
+extern int gMusicPrevLoadedEpisode;
+extern int gMusicPrevLoadedLevel;
+
+extern int gFrameClock;
+
+void QuitGame(void);
+void PreloadCache(void);
+void StartLevel(GAMEOPTIONS *gameOptions);
+void ProcessFrame(void);
+void ScanINIFiles(void);
+bool LoadArtFile(const char *pzFile);
+void LoadExtraArts(void);
+bool DemoRecordStatus(void);
+bool VanillaMode(void);
+bool fileExistsRFF(int id, const char* ext);
\ No newline at end of file
diff --git a/source/blood/src/callback.cpp b/source/blood/src/callback.cpp
new file mode 100644
index 000000000..223645a3f
--- /dev/null
+++ b/source/blood/src/callback.cpp
@@ -0,0 +1,731 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include "build.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "ai.h"
+#include "blood.h"
+#include "callback.h"
+#include "config.h"
+#include "db.h"
+#include "dude.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gameutil.h"
+#include "levels.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "trig.h"
+#include "triggers.h"
+#include "view.h"
+
+
+void sub_74C20(int nSprite) // 7
+{
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_15, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x10000);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x10000);
+ zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
+ }
+ evPost(nSprite, 3, 3, CALLBACK_ID_7);
+}
+
+void sub_74D04(int nSprite) // 15
+{
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_49, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
+ zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
+ }
+ evPost(nSprite, 3, 3, CALLBACK_ID_15);
+}
+
+void FinishHim(int nSprite) // 13
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (playerSeqPlaying(&gPlayer[pSprite->type-kDudePlayer1], 16) && pXSprite->target == gMe->at5b)
+ sndStartSample(3313, -1, 1, 0);
+}
+
+void FlameLick(int nSprite) // 0
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ for (int i = 0; i < 3; i++)
+ {
+ int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>3;
+ int nAngle = Random(2048);
+ int dx = mulscale30(nDist, Cos(nAngle));
+ int dy = mulscale30(nDist, Sin(nAngle));
+ int x = pSprite->x + dx;
+ int y = pSprite->y + dy;
+ int z = bottom-Random(bottom-top);
+ spritetype *pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(-dx);
+ yvel[pFX->index] = yvel[nSprite] + Random2(-dy);
+ zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
+ }
+ }
+ if (pXSprite->burnTime > 0)
+ evPost(nSprite, 3, 5, CALLBACK_ID_0);
+}
+
+void Remove(int nSprite) // 1
+{
+ spritetype *pSprite = &sprite[nSprite];
+ evKill(nSprite, 3);
+ if (pSprite->extra > 0)
+ seqKill(3, pSprite->extra);
+ sfxKill3DSound(pSprite, 0, -1);
+ DeleteSprite(nSprite);
+}
+
+void FlareBurst(int nSprite) // 2
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ int nAngle = getangle(xvel[nSprite], yvel[nSprite]);
+ int nRadius = 0x55555;
+ for (int i = 0; i < 8; i++)
+ {
+ spritetype *pSpawn = actSpawnSprite(pSprite, 5);
+ pSpawn->picnum = 2424;
+ pSpawn->shade = -128;
+ pSpawn->xrepeat = pSpawn->yrepeat = 32;
+ pSpawn->type = 303;
+ pSpawn->clipdist = 2;
+ pSpawn->owner = pSprite->owner;
+ int nAngle2 = (i<<11)/8;
+ int dx = 0;
+ int dy = mulscale30r(nRadius, Sin(nAngle2));
+ int dz = mulscale30r(nRadius, -Cos(nAngle2));
+ if (i&1)
+ {
+ dy >>= 1;
+ dz >>= 1;
+ }
+ RotateVector(&dx, &dy, nAngle);
+ xvel[pSpawn->index] += dx;
+ yvel[pSpawn->index] += dy;
+ zvel[pSpawn->index] += dz;
+ evPost(pSpawn->index, 3, 960, CALLBACK_ID_3);
+ }
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+}
+
+void FlareSpark(int nSprite) // 3
+{
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_28, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
+ zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
+ }
+ evPost(nSprite, 3, 4, CALLBACK_ID_3);
+}
+
+void FlareSparkLite(int nSprite) // 4
+{
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_28, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
+ zvel[pFX->index] = zvel[nSprite] - Random(0x1aaaa);
+ }
+ evPost(nSprite, 3, 12, CALLBACK_ID_4);
+}
+
+void ZombieSpurt(int nSprite) // 5
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, top, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x11111);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x11111);
+ zvel[pFX->index] = zvel[nSprite] - 0x6aaaa;
+ }
+ if (pXSprite->data1 > 0)
+ {
+ evPost(nSprite, 3, 4, CALLBACK_ID_5);
+ pXSprite->data1 -= 4;
+ }
+ else if (pXSprite->data2 > 0)
+ {
+ evPost(nSprite, 3, 60, CALLBACK_ID_5);
+ pXSprite->data1 = 40;
+ pXSprite->data2--;
+ }
+}
+
+void BloodSpurt(int nSprite) // 6
+{
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ pFX->ang = 0;
+ xvel[pFX->index] = xvel[nSprite]>>8;
+ yvel[pFX->index] = yvel[nSprite]>>8;
+ zvel[pFX->index] = zvel[nSprite]>>8;
+ }
+ evPost(nSprite, 3, 6, CALLBACK_ID_6);
+}
+
+void DynPuff(int nSprite) // 8
+{
+ spritetype *pSprite = &sprite[nSprite];
+ if (zvel[nSprite])
+ {
+ int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2;
+ int x = pSprite->x + mulscale30(nDist, Cos(pSprite->ang-512));
+ int y = pSprite->y + mulscale30(nDist, Sin(pSprite->ang-512));
+ int z = pSprite->z;
+ spritetype *pFX = gFX.fxSpawn(FX_7, pSprite->sectnum, x, y, z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite];
+ yvel[pFX->index] = yvel[nSprite];
+ zvel[pFX->index] = zvel[nSprite];
+ }
+ }
+ evPost(nSprite, 3, 12, CALLBACK_ID_8);
+}
+
+void Respawn(int nSprite) // 9
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pSprite->statnum != 8 && pSprite->statnum != 4)
+ ThrowError("Sprite %d is not on Respawn or Thing list\n", nSprite);
+ if (!(pSprite->hitag&16))
+ ThrowError("Sprite %d does not have the respawn attribute\n", nSprite);
+ switch (pXSprite->respawnPending)
+ {
+ case 1:
+ {
+ int nTime = mulscale16(actGetRespawnTime(pSprite), 0x4000);
+ pXSprite->respawnPending = 2;
+ evPost(nSprite, 3, nTime, CALLBACK_ID_9);
+ break;
+ }
+ case 2:
+ {
+ int nTime = mulscale16(actGetRespawnTime(pSprite), 0x2000);
+ pXSprite->respawnPending = 3;
+ evPost(nSprite, 3, nTime, CALLBACK_ID_9);
+ break;
+ }
+ case 3:
+ {
+ dassert(pSprite->owner != kStatRespawn);
+ dassert(pSprite->owner >= 0 && pSprite->owner < kMaxStatus);
+ ChangeSpriteStat(nSprite, pSprite->owner);
+ pSprite->type = pSprite->zvel;
+ pSprite->owner = -1;
+ pSprite->hitag &= ~16;
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+ pXSprite->respawnPending = 0;
+ pXSprite->burnTime = 0;
+ pXSprite->isTriggered = 0;
+ if (pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ {
+ int nType = pSprite->type-kDudeBase;
+ pSprite->x = baseSprite[nSprite].x;
+ pSprite->y = baseSprite[nSprite].y;
+ pSprite->z = baseSprite[nSprite].z;
+ pSprite->cstat |= 0x1101;
+ pSprite->clipdist = dudeInfo[nType].clipdist;
+ pXSprite->health = dudeInfo[nType].startHealth<<4;
+ if (gSysRes.Lookup(dudeInfo[nType].seqStartID, "SEQ"))
+ seqSpawn(dudeInfo[nType].seqStartID, 3, pSprite->extra, -1);
+ aiInitSprite(pSprite);
+ pXSprite->key = 0;
+ }
+ if (pSprite->type == 400)
+ {
+ pSprite->cstat |= 257;
+ pSprite->cstat &= (unsigned short)~32768;
+ }
+ gFX.fxSpawn(FX_29, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ sfxPlay3DSound(pSprite, 350, -1, 0);
+ break;
+ }
+ default:
+ ThrowError("Unexpected respawnPending value = %d", pXSprite->respawnPending);
+ break;
+ }
+}
+
+void PlayerBubble(int nSprite) // 10
+{
+ spritetype *pSprite = &sprite[nSprite];
+ if (IsPlayerSprite(pSprite))
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ dassert(pPlayer != NULL);
+ if (!pPlayer->at302)
+ return;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ for (int i = 0; i < (pPlayer->at302>>6); i++)
+ {
+ int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2;
+ int nAngle = Random(2048);
+ int x = pSprite->x + mulscale30(nDist, Cos(nAngle));
+ int y = pSprite->y + mulscale30(nDist, Sin(nAngle));
+ int z = bottom-Random(bottom-top);
+ spritetype *pFX = gFX.fxSpawn((FX_ID)(FX_23+Random(3)), pSprite->sectnum, x, y, z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
+ zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa);
+ }
+ }
+ evPost(nSprite, 3, 4, CALLBACK_ID_10);
+ }
+}
+
+void EnemyBubble(int nSprite) // 11
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ for (int i = 0; i < (klabs(zvel[nSprite])>>18); i++)
+ {
+ int nDist = (pSprite->xrepeat*(tilesiz[pSprite->picnum].x/2))>>2;
+ int nAngle = Random(2048);
+ int x = pSprite->x + mulscale30(nDist, Cos(nAngle));
+ int y = pSprite->y + mulscale30(nDist, Sin(nAngle));
+ int z = bottom-Random(bottom-top);
+ spritetype *pFX = gFX.fxSpawn((FX_ID)(FX_23+Random(3)), pSprite->sectnum, x, y, z, 0);
+ if (pFX)
+ {
+ xvel[pFX->index] = xvel[nSprite] + Random2(0x1aaaa);
+ yvel[pFX->index] = yvel[nSprite] + Random2(0x1aaaa);
+ zvel[pFX->index] = zvel[nSprite] + Random2(0x1aaaa);
+ }
+ }
+ evPost(nSprite, 3, 4, CALLBACK_ID_11);
+}
+
+void CounterCheck(int nSector) // 12
+{
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype *pSector = §or[nSector];
+ // By NoOne: edits for counter sector new features.
+ // remove check below, so every sector can be counter if command 12 (this callback) received.
+ //if (pSector->lotag != 619) return;
+ int nXSprite = pSector->extra;
+ if (nXSprite > 0)
+ {
+ XSECTOR *pXSector = &xsector[nXSprite];
+ int nReq = pXSector->waitTimeA;
+ int nType = pXSector->data;
+ if (nType && nReq)
+ {
+ int nCount = 0;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ if (sprite[nSprite].type == nType)
+ nCount++;
+ }
+ if (nCount >= nReq)
+ {
+
+ //pXSector->waitTimeA = 0; //do not reset necessary objects counter to zero
+ trTriggerSector(nSector, pXSector, 1);
+ pXSector->locked = 1; //lock sector, so it can be opened again later
+ }
+ else
+ evPost(nSector, 6, 5, CALLBACK_ID_12);
+ }
+ }
+}
+
+void sub_76140(int nSprite) // 14
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ pSprite->z += floorZ-bottom;
+ int nAngle = Random(2048);
+ int nDist = Random(16)<<4;
+ int x = pSprite->x+mulscale28(nDist, Cos(nAngle));
+ int y = pSprite->y+mulscale28(nDist, Sin(nAngle));
+ gFX.fxSpawn(FX_48, pSprite->sectnum, x, y, pSprite->z, 0);
+ if (pSprite->ang == 1024)
+ {
+ int nChannel = 28+(pSprite->index&2);
+ dassert(nChannel < 32);
+ sfxPlay3DSound(pSprite, 385, nChannel, 1);
+ }
+ if (Chance(0x5000))
+ {
+ spritetype *pFX = gFX.fxSpawn(FX_36, pSprite->sectnum, x, y, floorZ-64, 0);
+ if (pFX)
+ pFX->ang = nAngle;
+ }
+ gFX.sub_73FFC(nSprite);
+}
+
+void sub_7632C(spritetype *pSprite)
+{
+ xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0;
+ if (pSprite->extra > 0)
+ seqKill(3, pSprite->extra);
+ sfxKill3DSound(pSprite, -1, -1);
+ switch (pSprite->type)
+ {
+ case 37:
+ case 38:
+ case 39:
+ pSprite->picnum = 2465;
+ break;
+ case 40:
+ case 41:
+ case 42:
+ pSprite->picnum = 2464;
+ break;
+ }
+ pSprite->type = 51;
+ pSprite->xrepeat = pSprite->yrepeat = 10;
+}
+
+int dword_13B32C[] = { 608, 609, 611 };
+int dword_13B338[] = { 610, 612 };
+
+void sub_763BC(int nSprite) // 16
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ pSprite->z += floorZ-bottom;
+ int zv = zvel[nSprite]-velFloor[pSprite->sectnum];
+ if (zv > 0)
+ {
+ actFloorBounceVector((int*)&xvel[nSprite], (int*)&yvel[nSprite], &zv, pSprite->sectnum, 0x9000);
+ zvel[nSprite] = zv;
+ if (velFloor[pSprite->sectnum] == 0 && klabs(zvel[nSprite]) < 0x20000)
+ {
+ sub_7632C(pSprite);
+ return;
+ }
+ int nChannel = 28+(pSprite->index&2);
+ dassert(nChannel < 32);
+ if (pSprite->type >= 37 && pSprite->type <= 39)
+ {
+ Random(3);
+ sfxPlay3DSound(pSprite, 608+Random(2), nChannel, 1);
+ }
+ else
+ sfxPlay3DSound(pSprite, dword_13B338[Random(2)], nChannel, 1);
+ }
+ else if (zvel[nSprite] == 0)
+ sub_7632C(pSprite);
+}
+
+void sub_765B8(int nSprite) // 17
+{
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->owner >= 0 && pSprite->owner < kMaxSprites)
+ {
+ spritetype *pOwner = &sprite[pSprite->owner];
+ XSPRITE *pXOwner = &xsprite[pOwner->extra];
+ switch (pSprite->type)
+ {
+ case 147:
+ trTriggerSprite(pOwner->index, pXOwner, 1);
+ sndStartSample(8003, 255, 2, 0);
+ viewSetMessage("Blue Flag returned to base.");
+ break;
+ case 148:
+ trTriggerSprite(pOwner->index, pXOwner, 1);
+ sndStartSample(8002, 255, 2, 0);
+ viewSetMessage("Red Flag returned to base.");
+ break;
+ }
+ }
+ evPost(pSprite->index, 3, 0, CALLBACK_ID_1);
+}
+
+void sub_766B8(int nSprite) // 19
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist, CLIPMASK0);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ pSprite->z += floorZ-bottom;
+ int nAngle = Random(2048);
+ int nDist = Random(16)<<4;
+ int x = pSprite->x+mulscale28(nDist, Cos(nAngle));
+ int y = pSprite->y+mulscale28(nDist, Sin(nAngle));
+ if (pSprite->ang == 1024)
+ {
+ int nChannel = 28+(pSprite->index&2);
+ dassert(nChannel < 32);
+ sfxPlay3DSound(pSprite, 385, nChannel, 1);
+ }
+ spritetype *pFX = NULL;
+ if (pSprite->type == 53 || pSprite->type == 430)
+ {
+ if (Chance(0x500) || pSprite->type == 430)
+ pFX = gFX.fxSpawn(FX_55, pSprite->sectnum, x, y, floorZ-64, 0);
+ if (pFX)
+ pFX->ang = nAngle;
+ }
+ else
+ {
+ pFX = gFX.fxSpawn(FX_32, pSprite->sectnum, x, y, floorZ-64, 0);
+ if (pFX)
+ pFX->ang = nAngle;
+ }
+ gFX.sub_73FFC(nSprite);
+}
+
+void sub_768E8(int nSprite) // 18
+{
+ spritetype *pSprite = &sprite[nSprite];
+ spritetype *pFX;
+ if (pSprite->type == 53)
+ pFX = gFX.fxSpawn(FX_53, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ else
+ pFX = gFX.fxSpawn(FX_54, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pFX)
+ {
+ pFX->ang = 0;
+ xvel[pFX->index] = xvel[nSprite]>>8;
+ yvel[pFX->index] = yvel[nSprite]>>8;
+ zvel[pFX->index] = zvel[nSprite]>>8;
+ }
+ evPost(nSprite, 3, 6, CALLBACK_ID_18);
+}
+
+void sub_769B4(int nSprite) // 19
+{
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 4 && !(pSprite->hitag & 32)) {
+ switch (pSprite->lotag) {
+ case 431:
+ case kGDXThingCustomDudeLifeLeech:
+ xsprite[pSprite->extra].stateTimer = 0;
+ break;
+ }
+ }
+}
+
+void sub_76A08(spritetype *pSprite, spritetype *pSprite2, PLAYER *pPlayer)
+{
+ int top, bottom;
+ int nSprite = pSprite->index;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ pSprite->x = pSprite2->x;
+ pSprite->y = pSprite2->y;
+ pSprite->z = sector[pSprite2->sectnum].floorz-(bottom-pSprite->z);
+ pSprite->ang = pSprite2->ang;
+ ChangeSpriteSect(nSprite, pSprite2->sectnum);
+ sfxPlay3DSound(pSprite2, 201, -1, 0);
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+ viewBackupSpriteLoc(nSprite, pSprite);
+ if (pPlayer)
+ {
+ playerResetInertia(pPlayer);
+ pPlayer->at6b = pPlayer->at73 = 0;
+ }
+}
+
+void sub_76B78(int nSprite)
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int nOwner = actSpriteOwnerToSpriteId(pSprite);
+ if (nOwner < 0 || nOwner >= kMaxSprites)
+ {
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+ return;
+ }
+ spritetype *pOwner = &sprite[nOwner];
+ PLAYER *pPlayer;
+ if (IsPlayerSprite(pOwner))
+ pPlayer = &gPlayer[pOwner->type-kDudePlayer1];
+ else
+ pPlayer = NULL;
+ if (!pPlayer)
+ {
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+ return;
+ }
+ pSprite->ang = getangle(pOwner->x-pSprite->x, pOwner->y-pSprite->y);
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->data1 == 0)
+ {
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+ return;
+ }
+ int nSprite2, nNextSprite;
+ for (nSprite2 = headspritestat[6]; nSprite2 >= 0; nSprite2 = nNextSprite)
+ {
+ nNextSprite = nextspritestat[nSprite2];
+ if (nOwner == nSprite2)
+ continue;
+ spritetype *pSprite2 = &sprite[nSprite2];
+ int nXSprite2 = pSprite2->extra;
+ if (nXSprite2 > 0 && nXSprite2 < kMaxXSprites)
+ {
+ XSPRITE *pXSprite2 = &xsprite[nXSprite2];
+ PLAYER *pPlayer2;
+ if (IsPlayerSprite(pSprite2))
+ pPlayer2 = &gPlayer[pSprite2->type-kDudePlayer1];
+ else
+ pPlayer2 = NULL;
+ if (pXSprite2->health > 0 && (pPlayer2 || pXSprite2->key == 0))
+ {
+ if (pPlayer2)
+ {
+ if (gGameOptions.nGameType == 1)
+ continue;
+ if (gGameOptions.nGameType == 3 && pPlayer->at2ea == pPlayer2->at2ea)
+ continue;
+ int t = 0x8000/ClipLow(gNetPlayers-1, 1);
+ if (!powerupCheck(pPlayer2, 14))
+ t += ((3200-pPlayer2->at33e[2])<<15)/3200;
+ if (Chance(t) || nNextSprite < 0)
+ {
+ int nDmg = actDamageSprite(nOwner, pSprite2, DAMAGE_TYPE_5, pXSprite->data1<<4);
+ pXSprite->data1 = ClipLow(pXSprite->data1-nDmg, 0);
+ sub_76A08(pSprite2, pSprite, pPlayer2);
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+ return;
+ }
+ }
+ else
+ {
+ int vd = 0x2666;
+ switch (pSprite2->type)
+ {
+ case 218:
+ case 219:
+ case 220:
+ case 250:
+ case 251:
+ vd = 0x147;
+ break;
+ case 205:
+ case 221:
+ case 222:
+ case 223:
+ case 224:
+ case 225:
+ case 226:
+ case 227:
+ case 228:
+ case 229:
+ case 239:
+ case 240:
+ case 241:
+ case 242:
+ case 243:
+ case 244:
+ case 245:
+ case 252:
+ case 253:
+ vd = 0;
+ break;
+ }
+ if (vd && (Chance(vd) || nNextSprite < 0))
+ {
+ sub_76A08(pSprite2, pSprite, NULL);
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+ return;
+ }
+ }
+ }
+ }
+ }
+ pXSprite->data1 = ClipLow(pXSprite->data1-1, 0);
+ evPost(nSprite, 3, 0, CALLBACK_ID_1);
+ }
+}
+
+void(*gCallback[kCallbackMax])(int) =
+{
+ FlameLick,
+ Remove,
+ FlareBurst,
+ FlareSpark,
+ FlareSparkLite,
+ ZombieSpurt,
+ BloodSpurt,
+ sub_74C20,
+ DynPuff,
+ Respawn,
+ PlayerBubble,
+ EnemyBubble,
+ CounterCheck,
+ FinishHim,
+ sub_76140,
+ sub_74D04,
+ sub_763BC,
+ sub_765B8,
+ sub_768E8,
+ sub_766B8,
+ sub_769B4,
+ sub_76B78
+};
diff --git a/source/blood/src/callback.h b/source/blood/src/callback.h
new file mode 100644
index 000000000..c6ad73654
--- /dev/null
+++ b/source/blood/src/callback.h
@@ -0,0 +1,53 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+
+enum CALLBACK_ID {
+ CALLBACK_ID_NONE = -1,
+ CALLBACK_ID_0 = 0,
+ CALLBACK_ID_1,
+ CALLBACK_ID_2,
+ CALLBACK_ID_3,
+ CALLBACK_ID_4,
+ CALLBACK_ID_5,
+ CALLBACK_ID_6,
+ CALLBACK_ID_7,
+ CALLBACK_ID_8,
+ CALLBACK_ID_9,
+ CALLBACK_ID_10,
+ CALLBACK_ID_11,
+ CALLBACK_ID_12,
+ CALLBACK_ID_13,
+ CALLBACK_ID_14,
+ CALLBACK_ID_15,
+ CALLBACK_ID_16,
+ CALLBACK_ID_17,
+ CALLBACK_ID_18,
+ CALLBACK_ID_19,
+ CALLBACK_ID_20,
+ CALLBACK_ID_21,
+ kCallbackMax
+};
+
+extern void (*gCallback[kCallbackMax])(int);
\ No newline at end of file
diff --git a/source/blood/src/choke.cpp b/source/blood/src/choke.cpp
new file mode 100644
index 000000000..bf10091e9
--- /dev/null
+++ b/source/blood/src/choke.cpp
@@ -0,0 +1,136 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#include "common_game.h"
+#include "blood.h"
+#include "choke.h"
+#include "globals.h"
+#include "levels.h"
+#include "player.h"
+#include "qav.h"
+#include "resource.h"
+
+
+void CChoke::sub_83F54(char *a1, int _x, int _y, void (*a2)(PLAYER*))
+{
+ at14 = _x;
+ at18 = _y;
+ at0 = a1;
+ at1c = a2;
+ if (!at4 && at0)
+ {
+ at4 = gSysRes.Lookup(at0, "QAV");
+ if (!at4)
+ ThrowError("Could not load QAV %s\n", at0);
+ at8 = (QAV*)gSysRes.Lock(at4);
+ at8->nSprite = -1;
+ at8->x = at14;
+ at8->y = at18;
+ at8->Preload();
+ sub_84218();
+ }
+}
+
+void CChoke::sub_83ff0(int a1, void(*a2)(PLAYER*))
+{
+ at0 = NULL;
+ at1c = a2;
+ if (!at4 && a1 != -1)
+ {
+ at4 = gSysRes.Lookup(a1, "QAV");
+ if (!at4)
+ ThrowError("Could not load QAV %d\n", a1);
+ at8 = (QAV*)gSysRes.Lock(at4);
+ at8->nSprite = -1;
+ at8->x = at14;
+ at8->y = at18;
+ at8->Preload();
+ sub_84218();
+ }
+}
+
+void CChoke::sub_84080(char *a1, void(*a2)(PLAYER*))
+{
+ at0 = a1;
+ at1c = a2;
+ if (!at4 && at0)
+ {
+ at4 = gSysRes.Lookup(at0, "QAV");
+ if (!at4)
+ ThrowError("Could not load QAV %s\n", at0);
+ at8 = (QAV*)gSysRes.Lock(at4);
+ at8->nSprite = -1;
+ at8->x = at14;
+ at8->y = at18;
+ at8->Preload();
+ sub_84218();
+ }
+}
+
+void CChoke::sub_84110(int x, int y)
+{
+ if (!at4)
+ return;
+ int v4 = gFrameClock;
+ gFrameClock = gGameClock;
+ at8->x = x;
+ at8->y = y;
+ int vd = totalclock-at10;
+ at10 = totalclock;
+ atc -= vd;
+ if (atc <= 0 || atc > at8->at10)
+ atc = at8->at10;
+ int vdi = at8->at10-atc;
+ at8->Play(vdi-vd, vdi, -1, NULL);
+ int vb = windowxy1.x;
+ int v10 = windowxy1.y;
+ int vc = windowxy2.x;
+ int v8 = windowxy2.y;
+ windowxy1.x = windowxy1.y = 0;
+ windowxy2.x = xdim-1;
+ windowxy2.y = ydim-1;
+ at8->Draw(vdi, 10, 0, 0);
+ windowxy1.x = vb;
+ windowxy1.y = v10;
+ windowxy2.x = vc;
+ windowxy2.y = v8;
+ gFrameClock = v4;
+}
+
+void CChoke::sub_84218()
+{
+ atc = at8->at10;
+ at10 = totalclock;
+}
+
+void sub_84230(PLAYER *pPlayer)
+{
+ int t = gGameOptions.nDifficulty+2;
+ if (pPlayer->at372 < 64)
+ pPlayer->at372 = ClipHigh(pPlayer->at372+t, 64);
+ if (pPlayer->at372 > (7-gGameOptions.nDifficulty)*5)
+ pPlayer->at36a = ClipHigh(pPlayer->at36a+t*4, 128);
+}
+
+CChoke gChoke;
\ No newline at end of file
diff --git a/source/blood/src/choke.h b/source/blood/src/choke.h
new file mode 100644
index 000000000..34f7c5f54
--- /dev/null
+++ b/source/blood/src/choke.h
@@ -0,0 +1,61 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "common_game.h"
+#include "player.h"
+#include "qav.h"
+#include "resource.h"
+
+class CChoke
+{
+public:
+ CChoke()
+ {
+ at0 = NULL;
+ at4 = NULL;
+ at8 = NULL;
+ atc = 0;
+ at10 = 0;
+ at1c = NULL;
+ at14 = 0;
+ at18 = 0;
+ };
+ void sub_83F54(char *a1, int _x, int _y, void(*a2)(PLAYER*));
+ void sub_83ff0(int a1, void(*a2)(PLAYER*));
+ void sub_84080(char *a1, void(*a2)(PLAYER*));
+ void sub_84110(int x, int y);
+ void sub_84218();
+ char *at0;
+ DICTNODE *at4;
+ QAV *at8;
+ int atc;
+ int at10;
+ int at14;
+ int at18;
+ void(*at1c)(PLAYER *);
+};
+
+void sub_84230(PLAYER*);
+
+extern CChoke gChoke;
diff --git a/source/blood/src/common.cpp b/source/blood/src/common.cpp
new file mode 100644
index 000000000..37b5164e9
--- /dev/null
+++ b/source/blood/src/common.cpp
@@ -0,0 +1,748 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+//
+// Common non-engine code/data for EDuke32 and Mapster32
+//
+
+#include "compat.h"
+#include "build.h"
+#include "baselayer.h"
+#include "palette.h"
+
+#ifdef _WIN32
+# define NEED_SHLWAPI_H
+# include "windows_inc.h"
+# include "winbits.h"
+# ifndef KEY_WOW64_64KEY
+# define KEY_WOW64_64KEY 0x0100
+# endif
+# ifndef KEY_WOW64_32KEY
+# define KEY_WOW64_32KEY 0x0200
+# endif
+#elif defined __APPLE__
+# include "osxbits.h"
+#endif
+
+#include "common.h"
+#include "common_game.h"
+
+// g_grpNamePtr can ONLY point to a malloc'd block (length BMAX_PATH)
+char *g_grpNamePtr = NULL;
+
+const char *G_DefaultGrpFile(void)
+{
+ return "nblood.pk3";
+}
+
+const char *G_DefaultDefFile(void)
+{
+ return "blood.def";
+}
+
+const char *G_GrpFile(void)
+{
+ return (g_grpNamePtr == NULL) ? G_DefaultGrpFile() : g_grpNamePtr;
+}
+
+const char *G_DefFile(void)
+{
+ return (g_defNamePtr == NULL) ? G_DefaultDefFile() : g_defNamePtr;
+}
+
+static char g_rootDir[BMAX_PATH];
+
+int g_useCwd;
+int32_t g_groupFileHandle;
+
+void G_ExtPreInit(int32_t argc,char const * const * argv)
+{
+ g_useCwd = G_CheckCmdSwitch(argc, argv, "-usecwd");
+
+#ifdef _WIN32
+ GetModuleFileName(NULL,g_rootDir,BMAX_PATH);
+ Bcorrectfilename(g_rootDir,1);
+ //chdir(g_rootDir);
+#else
+ getcwd(g_rootDir,BMAX_PATH);
+ strcat(g_rootDir,"/");
+#endif
+}
+
+void G_ExtInit(void)
+{
+ char cwd[BMAX_PATH];
+
+#ifdef EDUKE32_OSX
+ char *appdir = Bgetappdir();
+ addsearchpath(appdir);
+ Bfree(appdir);
+#endif
+
+ if (getcwd(cwd,BMAX_PATH) && Bstrcmp(cwd,"/") != 0)
+ addsearchpath(cwd);
+
+ if (CommandPaths)
+ {
+ int32_t i;
+ struct strllist *s;
+ while (CommandPaths)
+ {
+ s = CommandPaths->next;
+ i = addsearchpath(CommandPaths->str);
+ if (i < 0)
+ {
+ initprintf("Failed adding %s for game data: %s\n", CommandPaths->str,
+ i==-1 ? "not a directory" : "no such directory");
+ }
+
+ Bfree(CommandPaths->str);
+ Bfree(CommandPaths);
+ CommandPaths = s;
+ }
+ }
+
+#if defined(_WIN32)
+ if (!access("user_profiles_enabled", F_OK))
+#else
+ if (g_useCwd == 0 && access("user_profiles_disabled", F_OK))
+#endif
+ {
+ char *homedir;
+ int32_t asperr;
+
+ if ((homedir = Bgethomedir()))
+ {
+ Bsnprintf(cwd,sizeof(cwd),"%s/"
+#if defined(_WIN32)
+ APPNAME
+#elif defined(GEKKO)
+ "apps/" APPBASENAME
+#else
+ ".config/" APPBASENAME
+#endif
+ ,homedir);
+ asperr = addsearchpath(cwd);
+ if (asperr == -2)
+ {
+ if (Bmkdir(cwd,S_IRWXU) == 0) asperr = addsearchpath(cwd);
+ else asperr = -1;
+ }
+ if (asperr == 0)
+ Bchdir(cwd);
+ Bfree(homedir);
+ }
+ }
+}
+
+static int32_t G_TryLoadingGrp(char const * const grpfile)
+{
+ int32_t i;
+
+ if ((i = initgroupfile(grpfile)) == -1)
+ initprintf("Warning: could not find main data file \"%s\"!\n", grpfile);
+ else
+ initprintf("Using \"%s\" as main game data file.\n", grpfile);
+
+ return i;
+}
+
+void G_LoadGroups(int32_t autoload)
+{
+ if (g_modDir[0] != '/')
+ {
+ char cwd[BMAX_PATH];
+
+ Bstrcat(g_rootDir, g_modDir);
+ addsearchpath(g_rootDir);
+ // addsearchpath(mod_dir);
+
+ char path[BMAX_PATH];
+
+ if (getcwd(cwd, BMAX_PATH))
+ {
+ Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir);
+ if (!Bstrcmp(g_rootDir, path))
+ {
+ if (addsearchpath(path) == -2)
+ if (Bmkdir(path, S_IRWXU) == 0)
+ addsearchpath(path);
+ }
+ }
+
+ }
+ const char *grpfile = G_GrpFile();
+ G_TryLoadingGrp(grpfile);
+
+ if (autoload)
+ {
+ G_LoadGroupsInDir("autoload");
+
+ //if (i != -1)
+ // G_DoAutoload(grpfile);
+ }
+
+ if (g_modDir[0] != '/')
+ G_LoadGroupsInDir(g_modDir);
+
+ if (g_defNamePtr == NULL)
+ {
+ const char *tmpptr = getenv("BLOODDEF");
+ if (tmpptr)
+ {
+ clearDefNamePtr();
+ g_defNamePtr = dup_filename(tmpptr);
+ initprintf("Using \"%s\" as definitions file\n", g_defNamePtr);
+ }
+ }
+
+ loaddefinitions_game(G_DefFile(), TRUE);
+
+ struct strllist *s;
+
+ int const bakpathsearchmode = pathsearchmode;
+ pathsearchmode = 1;
+
+ while (CommandGrps)
+ {
+ int32_t j;
+
+ s = CommandGrps->next;
+
+ if ((j = initgroupfile(CommandGrps->str)) == -1)
+ initprintf("Could not find file \"%s\".\n", CommandGrps->str);
+ else
+ {
+ g_groupFileHandle = j;
+ initprintf("Using file \"%s\" as game data.\n", CommandGrps->str);
+ if (autoload)
+ G_DoAutoload(CommandGrps->str);
+ }
+
+ Bfree(CommandGrps->str);
+ Bfree(CommandGrps);
+ CommandGrps = s;
+ }
+ pathsearchmode = bakpathsearchmode;
+}
+
+#if defined _WIN32
+static int G_ReadRegistryValue(char const * const SubKey, char const * const Value, char * const Output, DWORD * OutputSize)
+{
+ // KEY_WOW64_32KEY gets us around Wow6432Node on 64-bit builds
+ REGSAM const wow64keys[] = { KEY_WOW64_32KEY, KEY_WOW64_64KEY };
+
+ for (auto &wow64key : wow64keys)
+ {
+ HKEY hkey;
+ LONG keygood = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NULL, 0, KEY_READ | wow64key, &hkey);
+
+ if (keygood != ERROR_SUCCESS)
+ continue;
+
+ LONG retval = SHGetValueA(hkey, SubKey, Value, NULL, Output, OutputSize);
+
+ RegCloseKey(hkey);
+
+ if (retval == ERROR_SUCCESS)
+ return 1;
+ }
+
+ return 0;
+}
+#endif
+
+#ifndef EDUKE32_TOUCH_DEVICES
+#if defined EDUKE32_OSX || defined __linux__ || defined EDUKE32_BSD
+static void G_AddSteamPaths(const char *basepath)
+{
+ UNREFERENCED_PARAMETER(basepath);
+#if 0
+ char buf[BMAX_PATH];
+
+ // PORT-TODO:
+ // Duke Nukem 3D: Megaton Edition (Steam)
+ Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot", basepath);
+ addsearchpath(buf);
+ Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot/addons/dc", basepath);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot/addons/nw", basepath);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot/addons/vacation", basepath);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+
+ // Duke Nukem 3D (3D Realms Anthology (Steam) / Kill-A-Ton Collection 2015)
+#if defined EDUKE32_OSX
+ Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/Duke Nukem 3D.app/drive_c/Program Files/Duke Nukem 3D", basepath);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+#endif
+#endif
+}
+
+// A bare-bones "parser" for Valve's KeyValues VDF format.
+// There is no guarantee this will function properly with ill-formed files.
+static void KeyValues_SkipWhitespace(char **vdfbuf, char * const vdfbufend)
+{
+ while (((*vdfbuf)[0] == ' ' || (*vdfbuf)[0] == '\n' || (*vdfbuf)[0] == '\r' || (*vdfbuf)[0] == '\t' || (*vdfbuf)[0] == '\0') && *vdfbuf < vdfbufend)
+ (*vdfbuf)++;
+
+ // comments
+ if ((*vdfbuf) + 2 < vdfbufend && (*vdfbuf)[0] == '/' && (*vdfbuf)[1] == '/')
+ {
+ while ((*vdfbuf)[0] != '\n' && (*vdfbuf)[0] != '\r' && *vdfbuf < vdfbufend)
+ (*vdfbuf)++;
+
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+ }
+}
+static void KeyValues_SkipToEndOfQuotedToken(char **vdfbuf, char * const vdfbufend)
+{
+ (*vdfbuf)++;
+ while ((*vdfbuf)[0] != '\"' && (*vdfbuf)[-1] != '\\' && *vdfbuf < vdfbufend)
+ (*vdfbuf)++;
+}
+static void KeyValues_SkipToEndOfUnquotedToken(char **vdfbuf, char * const vdfbufend)
+{
+ while ((*vdfbuf)[0] != ' ' && (*vdfbuf)[0] != '\n' && (*vdfbuf)[0] != '\r' && (*vdfbuf)[0] != '\t' && (*vdfbuf)[0] != '\0' && *vdfbuf < vdfbufend)
+ (*vdfbuf)++;
+}
+static void KeyValues_SkipNextWhatever(char **vdfbuf, char * const vdfbufend)
+{
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+
+ if (*vdfbuf == vdfbufend)
+ return;
+
+ if ((*vdfbuf)[0] == '{')
+ {
+ (*vdfbuf)++;
+ do
+ {
+ KeyValues_SkipNextWhatever(vdfbuf, vdfbufend);
+ }
+ while ((*vdfbuf)[0] != '}');
+ (*vdfbuf)++;
+ }
+ else if ((*vdfbuf)[0] == '\"')
+ KeyValues_SkipToEndOfQuotedToken(vdfbuf, vdfbufend);
+ else if ((*vdfbuf)[0] != '}')
+ KeyValues_SkipToEndOfUnquotedToken(vdfbuf, vdfbufend);
+
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+}
+static char* KeyValues_NormalizeToken(char **vdfbuf, char * const vdfbufend)
+{
+ char *token = *vdfbuf;
+
+ if ((*vdfbuf)[0] == '\"' && *vdfbuf < vdfbufend)
+ {
+ token++;
+
+ KeyValues_SkipToEndOfQuotedToken(vdfbuf, vdfbufend);
+ (*vdfbuf)[0] = '\0';
+
+ // account for escape sequences
+ char *writeseeker = token, *readseeker = token;
+ while (readseeker <= *vdfbuf)
+ {
+ if (readseeker[0] == '\\')
+ readseeker++;
+
+ writeseeker[0] = readseeker[0];
+
+ writeseeker++;
+ readseeker++;
+ }
+
+ return token;
+ }
+
+ KeyValues_SkipToEndOfUnquotedToken(vdfbuf, vdfbufend);
+ (*vdfbuf)[0] = '\0';
+
+ return token;
+}
+static void KeyValues_FindKey(char **vdfbuf, char * const vdfbufend, const char *token)
+{
+ char *ParentKey = KeyValues_NormalizeToken(vdfbuf, vdfbufend);
+ if (token != NULL) // pass in NULL to find the next key instead of a specific one
+ while (Bstrcmp(ParentKey, token) != 0 && *vdfbuf < vdfbufend)
+ {
+ KeyValues_SkipNextWhatever(vdfbuf, vdfbufend);
+ ParentKey = KeyValues_NormalizeToken(vdfbuf, vdfbufend);
+ }
+
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+}
+static int32_t KeyValues_FindParentKey(char **vdfbuf, char * const vdfbufend, const char *token)
+{
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+
+ // end of scope
+ if ((*vdfbuf)[0] == '}')
+ return 0;
+
+ KeyValues_FindKey(vdfbuf, vdfbufend, token);
+
+ // ignore the wrong type
+ while ((*vdfbuf)[0] != '{' && *vdfbuf < vdfbufend)
+ {
+ KeyValues_SkipNextWhatever(vdfbuf, vdfbufend);
+ KeyValues_FindKey(vdfbuf, vdfbufend, token);
+ }
+
+ if (*vdfbuf == vdfbufend)
+ return 0;
+
+ return 1;
+}
+static char* KeyValues_FindKeyValue(char **vdfbuf, char * const vdfbufend, const char *token)
+{
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+
+ // end of scope
+ if ((*vdfbuf)[0] == '}')
+ return NULL;
+
+ KeyValues_FindKey(vdfbuf, vdfbufend, token);
+
+ // ignore the wrong type
+ while ((*vdfbuf)[0] == '{' && *vdfbuf < vdfbufend)
+ {
+ KeyValues_SkipNextWhatever(vdfbuf, vdfbufend);
+ KeyValues_FindKey(vdfbuf, vdfbufend, token);
+ }
+
+ KeyValues_SkipWhitespace(vdfbuf, vdfbufend);
+
+ if (*vdfbuf == vdfbufend)
+ return NULL;
+
+ return KeyValues_NormalizeToken(vdfbuf, vdfbufend);
+}
+
+static void G_ParseSteamKeyValuesForPaths(const char *vdf)
+{
+ int32_t fd = Bopen(vdf, BO_RDONLY);
+ int32_t size = Bfilelength(fd);
+ char *vdfbufstart, *vdfbuf, *vdfbufend;
+
+ if (size <= 0)
+ return;
+
+ vdfbufstart = vdfbuf = (char*)Xmalloc(size);
+ size = (int32_t)Bread(fd, vdfbuf, size);
+ Bclose(fd);
+ vdfbufend = vdfbuf + size;
+
+ if (KeyValues_FindParentKey(&vdfbuf, vdfbufend, "LibraryFolders"))
+ {
+ char *result;
+ vdfbuf++;
+ while ((result = KeyValues_FindKeyValue(&vdfbuf, vdfbufend, NULL)) != NULL)
+ G_AddSteamPaths(result);
+ }
+
+ Bfree(vdfbufstart);
+}
+#endif
+#endif
+
+void G_AddSearchPaths(void)
+{
+#ifndef EDUKE32_TOUCH_DEVICES
+#if defined __linux__ || defined EDUKE32_BSD
+ char buf[BMAX_PATH];
+ char *homepath = Bgethomedir();
+
+ Bsnprintf(buf, sizeof(buf), "%s/.steam/steam", homepath);
+ G_AddSteamPaths(buf);
+
+ Bsnprintf(buf, sizeof(buf), "%s/.steam/steam/steamapps/libraryfolders.vdf", homepath);
+ G_ParseSteamKeyValuesForPaths(buf);
+
+ Bfree(homepath);
+
+ addsearchpath("/usr/share/games/nblood");
+ addsearchpath("/usr/local/share/games/nblood");
+#elif defined EDUKE32_OSX
+ char buf[BMAX_PATH];
+ int32_t i;
+ char *applications[] = { osx_getapplicationsdir(0), osx_getapplicationsdir(1) };
+ char *support[] = { osx_getsupportdir(0), osx_getsupportdir(1) };
+
+ for (i = 0; i < 2; i++)
+ {
+ Bsnprintf(buf, sizeof(buf), "%s/Steam", support[i]);
+ G_AddSteamPaths(buf);
+
+ Bsnprintf(buf, sizeof(buf), "%s/Steam/steamapps/libraryfolders.vdf", support[i]);
+ G_ParseSteamKeyValuesForPaths(buf);
+
+#if 0
+ // Duke Nukem 3D: Atomic Edition (GOG.com)
+ Bsnprintf(buf, sizeof(buf), "%s/Duke Nukem 3D.app/Contents/Resources/Duke Nukem 3D.boxer/C.harddisk", applications[i]);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+#endif
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ Bsnprintf(buf, sizeof(buf), "%s/NBlood", support[i]);
+ addsearchpath(buf);
+ }
+
+ for (i = 0; i < 2; i++)
+ {
+ Bfree(applications[i]);
+ Bfree(support[i]);
+ }
+#elif defined (_WIN32)
+ char buf[BMAX_PATH] = {0};
+ DWORD bufsize;
+ bool found = false;
+
+ // Blood: One Unit Whole Blood (Steam)
+ bufsize = sizeof(buf);
+ if (!found && G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 299030)", "InstallLocation", buf, &bufsize))
+ {
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ found = true;
+ }
+
+ // Blood: One Unit Whole Blood (GOG.com)
+ bufsize = sizeof(buf);
+ if (!found && G_ReadRegistryValue("SOFTWARE\\GOG.com\\GOGONEUNITONEBLOOD", "PATH", buf, &bufsize))
+ {
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ found = true;
+ }
+
+ // Blood: Fresh Supply (Steam)
+ bufsize = sizeof(buf);
+ if (!found && G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 1010750)", "InstallLocation", buf, &bufsize))
+ {
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ strncat(buf, R"(\addons\Cryptic Passage)", 23);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ found = true;
+ }
+
+ // Blood: Fresh Supply (GOG.com)
+ bufsize = sizeof(buf);
+ if (!found && G_ReadRegistryValue(R"(SOFTWARE\Wow6432Node\GOG.com\Games\1374469660)", "path", buf, &bufsize))
+ {
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ strncat(buf, R"(\addons\Cryptic Passage)", 23);
+ addsearchpath_user(buf, SEARCHPATH_REMOVE);
+ found = true;
+ }
+#endif
+#endif
+}
+
+void G_CleanupSearchPaths(void)
+{
+ removesearchpaths_withuser(SEARCHPATH_REMOVE);
+}
+
+//////////
+
+struct strllist *CommandPaths, *CommandGrps;
+
+void G_AddGroup(const char *buffer)
+{
+ char buf[BMAX_PATH];
+
+ struct strllist *s = (struct strllist *)Xcalloc(1,sizeof(struct strllist));
+
+ Bstrcpy(buf, buffer);
+
+ if (Bstrchr(buf,'.') == 0)
+ Bstrcat(buf,".grp");
+
+ s->str = Xstrdup(buf);
+
+ if (CommandGrps)
+ {
+ struct strllist *t;
+ for (t = CommandGrps; t->next; t=t->next) ;
+ t->next = s;
+ return;
+ }
+ CommandGrps = s;
+}
+
+void G_AddPath(const char *buffer)
+{
+ struct strllist *s = (struct strllist *)Xcalloc(1,sizeof(struct strllist));
+ s->str = Xstrdup(buffer);
+
+ if (CommandPaths)
+ {
+ struct strllist *t;
+ for (t = CommandPaths; t->next; t=t->next) ;
+ t->next = s;
+ return;
+ }
+ CommandPaths = s;
+}
+
+//////////
+
+// loads all group (grp, zip, pk3/4) files in the given directory
+void G_LoadGroupsInDir(const char *dirname)
+{
+ static const char *extensions[] = { "*.grp", "*.zip", "*.ssi", "*.pk3", "*.pk4" };
+ char buf[BMAX_PATH];
+ fnlist_t fnlist = FNLIST_INITIALIZER;
+
+ for (auto & extension : extensions)
+ {
+ CACHE1D_FIND_REC *rec;
+
+ fnlist_getnames(&fnlist, dirname, extension, -1, 0);
+
+ for (rec=fnlist.findfiles; rec; rec=rec->next)
+ {
+ Bsnprintf(buf, sizeof(buf), "%s/%s", dirname, rec->name);
+ initprintf("Using group file \"%s\".\n", buf);
+ initgroupfile(buf);
+ }
+
+ fnlist_clearnames(&fnlist);
+ }
+}
+
+void G_DoAutoload(const char *dirname)
+{
+ char buf[BMAX_PATH];
+
+ Bsnprintf(buf, sizeof(buf), "autoload/%s", dirname);
+ G_LoadGroupsInDir(buf);
+}
+
+//////////
+
+#ifdef FORMAT_UPGRADE_ELIGIBLE
+
+static int32_t S_TryFormats(char * const testfn, char * const fn_suffix, char const searchfirst)
+{
+#ifdef HAVE_FLAC
+ {
+ Bstrcpy(fn_suffix, ".flac");
+ int32_t const fp = kopen4loadfrommod(testfn, searchfirst);
+ if (fp >= 0)
+ return fp;
+ }
+#endif
+
+#ifdef HAVE_VORBIS
+ {
+ Bstrcpy(fn_suffix, ".ogg");
+ int32_t const fp = kopen4loadfrommod(testfn, searchfirst);
+ if (fp >= 0)
+ return fp;
+ }
+#endif
+
+ return -1;
+}
+
+static int32_t S_TryExtensionReplacements(char * const testfn, char const searchfirst, uint8_t const ismusic)
+{
+ char * extension = Bstrrchr(testfn, '.');
+ char * const fn_end = Bstrchr(testfn, '\0');
+
+ // ex: grabbag.voc --> grabbag_voc.*
+ if (extension != NULL)
+ {
+ *extension = '_';
+
+ int32_t const fp = S_TryFormats(testfn, fn_end, searchfirst);
+ if (fp >= 0)
+ return fp;
+ }
+ else
+ {
+ extension = fn_end;
+ }
+
+ // ex: grabbag.mid --> grabbag.*
+ if (ismusic)
+ {
+ int32_t const fp = S_TryFormats(testfn, extension, searchfirst);
+ if (fp >= 0)
+ return fp;
+ }
+
+ return -1;
+}
+
+int32_t S_OpenAudio(const char *fn, char searchfirst, uint8_t const ismusic)
+{
+ int32_t const origfp = kopen4loadfrommod(fn, searchfirst);
+ char const *const origparent = origfp != -1 ? kfileparent(origfp) : NULL;
+ uint32_t const parentlength = origparent != NULL ? Bstrlen(origparent) : 0;
+
+ auto testfn = (char *)Xmalloc(Bstrlen(fn) + 12 + parentlength); // "music/" + overestimation of parent minus extension + ".flac" + '\0'
+
+ // look in ./
+ // ex: ./grabbag.mid
+ Bstrcpy(testfn, fn);
+ int32_t fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
+ if (fp >= 0)
+ goto success;
+
+ // look in ./music//
+ // ex: ./music/duke3d/grabbag.mid
+ // ex: ./music/nwinter/grabbag.mid
+ if (origparent != NULL)
+ {
+ char const * const parentextension = Bstrrchr(origparent, '.');
+ uint32_t const namelength = parentextension != NULL ? (unsigned)(parentextension - origparent) : parentlength;
+
+ Bsprintf(testfn, "music/%.*s/%s", namelength, origparent, fn);
+ fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
+ if (fp >= 0)
+ goto success;
+ }
+
+ // look in ./music/
+ // ex: ./music/grabbag.mid
+ Bsprintf(testfn, "music/%s", fn);
+ fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic);
+ if (fp >= 0)
+ goto success;
+
+ fp = origfp;
+success:
+ Bfree(testfn);
+ if (fp != origfp)
+ kclose(origfp);
+
+ return fp;
+}
+
+#endif
+
diff --git a/source/blood/src/common_game.h b/source/blood/src/common_game.h
new file mode 100644
index 000000000..3aab66ce9
--- /dev/null
+++ b/source/blood/src/common_game.h
@@ -0,0 +1,499 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "baselayer.h"
+#include "build.h"
+#include "cache1d.h"
+#include "common.h"
+#include "pragmas.h"
+#include "misc.h"
+#include "network.h"
+
+extern int g_useCwd;
+
+#ifndef APPNAME
+#define APPNAME "NBlood"
+#endif
+
+#ifndef APPBASENAME
+#define APPBASENAME "nblood"
+#endif
+
+#define BYTEVERSION 102
+#define EXEVERSION 101
+
+void _SetErrorLoc(const char *pzFile, int nLine);
+void _ThrowError(const char *pzFormat, ...);
+void __dassert(const char *pzExpr, const char *pzFile, int nLine);
+
+#define ThrowError(...) \
+ { \
+ _SetErrorLoc(__FILE__,__LINE__); \
+ _ThrowError(__VA_ARGS__); \
+ }
+
+#define dassert(x) if (!(x)) __dassert(#x,__FILE__,__LINE__)
+
+#define kMaxSectors 1024
+#define kMaxWalls 8192
+#define kMaxSprites 4096
+
+#define kMaxTiles MAXTILES
+#define kMaxStatus MAXSTATUS
+#define kMaxPlayers 8
+#define kMaxViewSprites maxspritesonscreen
+
+#define kMaxVoxels MAXVOXELS
+
+#define kTicRate 120
+#define kTicsPerFrame 4
+#define kTicsPerSec (kTicRate/kTicsPerFrame)
+
+// NUKE-TODO:
+#define OSDTEXT_DEFAULT "^00"
+#define OSDTEXT_DARKRED "^00"
+#define OSDTEXT_GREEN "^00"
+#define OSDTEXT_RED "^00"
+#define OSDTEXT_YELLOW "^00"
+
+#define OSDTEXT_BRIGHT "^S0"
+
+#define OSD_ERROR OSDTEXT_DARKRED OSDTEXT_BRIGHT
+
+enum BLOOD_GLOBALFLAGS {
+ BLOOD_FORCE_WIDELOADSCREEN = 1<<0,
+};
+
+enum searchpathtypes_t {
+ SEARCHPATH_REMOVE = 1<<0,
+};
+
+extern char *g_grpNamePtr;
+
+extern int loaddefinitions_game(const char *fn, int32_t preload);
+
+extern void G_AddSearchPaths(void);
+extern void G_CleanupSearchPaths(void);
+
+extern void G_ExtPreInit(int32_t argc, char const * const * argv);
+extern void G_ExtInit(void);
+
+void G_LoadGroupsInDir(const char *dirname);
+void G_DoAutoload(const char *dirname);
+extern void G_LoadGroups(int32_t autoload);
+
+#define G_ModDirSnprintf(buf, size, basename, ...) \
+ (((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/" basename, g_modDir, ##__VA_ARGS__) : Bsnprintf(buf, size, basename, ##__VA_ARGS__)) \
+ >= ((int32_t)size) - 1)
+
+#define G_ModDirSnprintfLite(buf, size, basename) \
+ ((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/%s", g_modDir, basename) : Bsnprintf(buf, size, "%s", basename))
+
+static inline void G_HandleAsync(void)
+{
+ handleevents();
+ netGetPackets();
+}
+
+#if defined HAVE_FLAC || defined HAVE_VORBIS
+# define FORMAT_UPGRADE_ELIGIBLE
+extern int32_t S_OpenAudio(const char *fn, char searchfirst, uint8_t ismusic);
+#else
+# define S_OpenAudio(fn, searchfirst, ismusic) kopen4loadfrommod(fn, searchfirst)
+#endif
+
+#pragma pack(push,1)
+
+#if 0
+struct sectortype
+{
+ short wallptr, wallnum;
+ int ceilingz, floorz;
+ unsigned short ceilingstat, floorstat;
+ short ceilingpicnum, ceilingheinum;
+ signed char ceilingshade;
+ char ceilingpal, ceilingxpanning, ceilingypanning;
+ short floorpicnum, floorheinum;
+ signed char floorshade;
+ char floorpal, floorxpanning, floorypanning;
+ char visibility, filler;
+ unsigned short lotag;
+ short hitag, extra;
+};
+
+struct walltype
+{
+ int x, y;
+ short point2, nextwall, nextsector;
+ unsigned short cstat;
+ short picnum, overpicnum;
+ signed char shade;
+ char pal, xrepeat, yrepeat, xpanning, ypanning;
+ short lotag, hitag, extra;
+};
+
+struct spritetype
+{
+ int x, y, z;
+ short cstat, picnum;
+ signed char shade;
+ char pal, clipdist, filler;
+ unsigned char xrepeat, yrepeat;
+ signed char xoffset, yoffset;
+ short sectnum, statnum;
+ short ang, owner, index, yvel, zvel;
+ short type, hitag, extra;
+};
+
+struct PICANM {
+ unsigned int animframes : 5;
+ unsigned int at0_5 : 1;
+ unsigned int animtype : 2;
+ signed int xoffset : 8;
+ signed int yoffset : 8;
+ unsigned int animspeed : 4;
+ unsigned int at3_4 : 3; // type
+ unsigned int at3_7 : 1; // filler
+};
+#endif
+
+struct LOCATION {
+ int x, y, z;
+ int ang;
+};
+
+struct POINT2D {
+ int x, y;
+};
+
+struct POINT3D {
+ int x, y, z;
+};
+
+struct VECTOR2D {
+ int dx, dy;
+};
+
+struct Aim {
+ int dx, dy, dz;
+};
+
+#pragma pack(pop)
+
+extern char qsprite_filler[], qsector_filler[];
+
+inline int ksgnf(float f)
+{
+ if (f < 0)
+ return -1;
+ if (f > 0)
+ return 1;
+ return 0;
+}
+
+inline int IncBy(int a, int b)
+{
+ a += b;
+ int q = a % b;
+ a -= q;
+ if (q < 0)
+ a -= b;
+ return a;
+}
+
+inline int DecBy(int a, int b)
+{
+ a--;
+ int q = a % b;
+ a -= q;
+ if (q < 0)
+ a -= b;
+ return a;
+}
+
+#if 0
+inline float IncByF(float a, float b)
+{
+ a += b;
+ float q = fmod(a, b);
+ a -= q;
+ if (q < 0)
+ a -= b;
+ return a;
+}
+
+inline float DecByF(float a, float b)
+{
+ //a--;
+ a -= fabs(b)*0.001;
+ float q = fmod(a, b);
+ a -= q;
+ if (q < 0)
+ a -= b;
+ return a;
+}
+#endif
+
+inline int ClipLow(int a, int b)
+{
+ if (a < b)
+ return b;
+ return a;
+}
+
+inline int ClipHigh(int a, int b)
+{
+ if (a >= b)
+ return b;
+ return a;
+}
+
+inline int ClipRange(int a, int b, int c)
+{
+ if (a < b)
+ return b;
+ if (a > c)
+ return c;
+ return a;
+}
+
+inline float ClipRangeF(float a, float b, float c)
+{
+ if (a < b)
+ return b;
+ if (a > c)
+ return c;
+ return a;
+}
+
+inline int interpolate(int a, int b, int c)
+{
+ return a+mulscale16(b-a,c);
+}
+
+inline int interpolateang(int a, int b, int c)
+{
+ return a+mulscale16(((b-a+1024)&2047)-1024, c);
+}
+
+inline fix16_t interpolateangfix16(fix16_t a, fix16_t b, int c)
+{
+ return a+mulscale16(((b-a+0x4000000)&0x7ffffff)-0x4000000, c);
+}
+
+inline char Chance(int a1)
+{
+ return wrand() < (a1>>1);
+}
+
+inline unsigned int Random(int a1)
+{
+ return mulscale(wrand(), a1, 15);
+}
+
+inline int Random2(int a1)
+{
+ return mulscale(wrand(), a1, 14)-a1;
+}
+
+inline int Random3(int a1)
+{
+ return mulscale(wrand()+wrand(), a1, 15) - a1;
+}
+
+inline unsigned int QRandom(int a1)
+{
+ return mulscale(qrand(), a1, 15);
+}
+
+inline int QRandom2(int a1)
+{
+ return mulscale(qrand(), a1, 14)-a1;
+}
+
+inline void SetBitString(char *pArray, int nIndex)
+{
+ pArray[nIndex>>3] |= 1<<(nIndex&7);
+}
+
+inline void ClearBitString(char *pArray, int nIndex)
+{
+ pArray[nIndex >> 3] &= ~(1 << (nIndex & 7));
+}
+
+inline char TestBitString(char *pArray, int nIndex)
+{
+ return pArray[nIndex>>3] & (1<<(nIndex&7));
+}
+
+inline int scale(int a1, int a2, int a3, int a4, int a5)
+{
+ return a4 + (a5-a4) * (a1-a2) / (a3-a2);
+}
+
+inline int mulscale16r(int a, int b)
+{
+ int64_t acc = 1<<(16-1);
+ acc += ((int64_t)a) * b;
+ return (int)(acc>>16);
+}
+
+inline int mulscale30r(int a, int b)
+{
+ int64_t acc = 1<<(30-1);
+ acc += ((int64_t)a) * b;
+ return (int)(acc>>30);
+}
+
+inline int dmulscale30r(int a, int b, int c, int d)
+{
+ int64_t acc = 1<<(30-1);
+ acc += ((int64_t)a) * b;
+ acc += ((int64_t)c) * d;
+ return (int)(acc>>30);
+}
+
+inline int approxDist(int dx, int dy)
+{
+ dx = klabs(dx);
+ dy = klabs(dy);
+ if (dx > dy)
+ dy = (3*dy)>>3;
+ else
+ dx = (3*dx)>>3;
+ return dx+dy;
+}
+
+class Rect {
+public:
+ int x1, y1, x2, y2;
+ Rect(int _x1, int _y1, int _x2, int _y2)
+ {
+ x1 = _x1; y1 = _y1; x2 = _x2; y2 = _y2;
+ }
+ bool isValid(void) const
+ {
+ return x1 < x2 && y1 < y2;
+ }
+ char isEmpty(void) const
+ {
+ return !(x1 < x2 && y1 < y2);
+ }
+ bool operator!(void) const
+ {
+ return isEmpty();
+ }
+
+ Rect & operator&=(Rect &pOther)
+ {
+ x1 = ClipLow(x1, pOther.x1);
+ y1 = ClipLow(y1, pOther.y1);
+ x2 = ClipHigh(x2, pOther.x2);
+ y2 = ClipHigh(y2, pOther.y2);
+ return *this;
+ }
+};
+
+class BitReader {
+public:
+ int nBitPos;
+ int nSize;
+ char *pBuffer;
+ BitReader(char *_pBuffer, int _nSize, int _nBitPos) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = _nBitPos; nSize -= nBitPos>>3; }
+ BitReader(char *_pBuffer, int _nSize) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = 0; }
+ int readBit()
+ {
+ if (nSize <= 0)
+ ThrowError("Buffer overflow");
+ int bit = ((*pBuffer)>>nBitPos)&1;
+ if (++nBitPos >= 8)
+ {
+ nBitPos = 0;
+ pBuffer++;
+ nSize--;
+ }
+ return bit;
+ }
+ void skipBits(int nBits)
+ {
+ nBitPos += nBits;
+ pBuffer += nBitPos>>3;
+ nSize -= nBitPos>>3;
+ nBitPos &= 7;
+ if ((nSize == 0 && nBitPos > 0) || nSize < 0)
+ ThrowError("Buffer overflow");
+ }
+ unsigned int readUnsigned(int nBits)
+ {
+ unsigned int n = 0;
+ dassert(nBits <= 32);
+ for (int i = 0; i < nBits; i++)
+ n += readBit()<>= 32-nBits;
+ return n;
+ }
+};
+
+class BitWriter {
+public:
+ int nBitPos;
+ int nSize;
+ char *pBuffer;
+ BitWriter(char *_pBuffer, int _nSize, int _nBitPos) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = _nBitPos; memset(pBuffer, 0, nSize); nSize -= nBitPos>>3; }
+ BitWriter(char *_pBuffer, int _nSize) { pBuffer = _pBuffer; nSize = _nSize; nBitPos = 0; memset(pBuffer, 0, nSize); }
+ void writeBit(int bit)
+ {
+ if (nSize <= 0)
+ ThrowError("Buffer overflow");
+ *pBuffer |= bit<= 8)
+ {
+ nBitPos = 0;
+ pBuffer++;
+ nSize--;
+ }
+ }
+ void skipBits(int nBits)
+ {
+ nBitPos += nBits;
+ pBuffer += nBitPos>>3;
+ nSize -= nBitPos>>3;
+ nBitPos &= 7;
+ if ((nSize == 0 && nBitPos > 0) || nSize < 0)
+ ThrowError("Buffer overflow");
+ }
+ void write(int nValue, int nBits)
+ {
+ dassert(nBits <= 32);
+ for (int i = 0; i < nBits; i++)
+ writeBit((nValue>>i)&1);
+ }
+};
+
diff --git a/source/blood/src/config.cpp b/source/blood/src/config.cpp
new file mode 100644
index 000000000..fd81aeb2b
--- /dev/null
+++ b/source/blood/src/config.cpp
@@ -0,0 +1,1079 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include "baselayer.h"
+#include "common_game.h"
+#include "build.h"
+#include "cache1d.h"
+#include "hash.h"
+#include "scriplib.h"
+#include "renderlayer.h"
+#include "function.h"
+#include "blood.h"
+#include "gamedefs.h"
+#include "config.h"
+#include "view.h"
+
+#ifdef __ANDROID__
+# include "android.h"
+#endif
+
+#if defined RENDERTYPESDL && defined SDL_TARGET && SDL_TARGET > 1
+# include "sdl_inc.h"
+#endif
+
+// we load this in to get default button and key assignments
+// as well as setting up function mappings
+
+#define __SETUP__ // JBF 20031211
+#include "_functio.h"
+
+hashtable_t h_gamefuncs = { NUMGAMEFUNCTIONS<<1, NULL };
+
+int32_t MAXCACHE1DSIZE = (96*1024*1024);
+
+int32_t MouseDeadZone, MouseBias;
+int32_t SmoothInput;
+int32_t MouseFunctions[MAXMOUSEBUTTONS][2];
+int32_t MouseDigitalFunctions[MAXMOUSEAXES][2];
+int32_t MouseAnalogueAxes[MAXMOUSEAXES];
+int32_t MouseAnalogueScale[MAXMOUSEAXES];
+int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2];
+int32_t JoystickDigitalFunctions[MAXJOYAXES][2];
+int32_t JoystickAnalogueAxes[MAXJOYAXES];
+int32_t JoystickAnalogueScale[MAXJOYAXES];
+int32_t JoystickAnalogueDead[MAXJOYAXES];
+int32_t JoystickAnalogueSaturate[MAXJOYAXES];
+uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2];
+int32_t scripthandle;
+int32_t setupread;
+int32_t SoundToggle;
+int32_t MusicToggle;
+int32_t MusicRestartsOnLoadToggle;
+int32_t CDAudioToggle;
+int32_t FXVolume;
+int32_t MusicVolume;
+int32_t CDVolume;
+int32_t NumVoices;
+int32_t NumChannels;
+int32_t NumBits;
+int32_t MixRate;
+int32_t ReverseStereo;
+int32_t MusicDevice;
+int32_t configversion;
+int32_t CheckForUpdates;
+int32_t LastUpdateCheck;
+int32_t useprecache;
+char CommbatMacro[MAXRIDECULE][MAXRIDECULELENGTH];
+char szPlayerName[MAXPLAYERNAME];
+int32_t gTurnSpeed;
+int32_t gDetail;
+int32_t gMouseAim;
+int32_t gAutoAim;
+int32_t gWeaponSwitch;
+int32_t gAutoRun;
+int32_t gViewInterpolate;
+int32_t gViewHBobbing;
+int32_t gViewVBobbing;
+int32_t gFollowMap;
+int32_t gOverlayMap;
+int32_t gRotateMap;
+int32_t gAimReticle;
+int32_t gSlopeTilting;
+int32_t gMessageState;
+int32_t gMessageCount;
+int32_t gMessageTime;
+int32_t gMessageFont;
+int32_t gbAdultContent;
+char gzAdultPassword[9];
+int32_t gDoppler;
+int32_t gShowWeapon;
+int32_t gMouseSensitivity;
+int32_t gMouseAiming;
+int32_t gMouseAimingFlipped;
+int32_t gRunKeyMode;
+bool gNoClip;
+bool gInfiniteAmmo;
+bool gFullMap;
+int32_t gUpscaleFactor;
+int32_t gBrightness;
+int32_t gLevelStats;
+int32_t gPowerupDuration;
+int32_t gShowMapTitle;
+int32_t gFov;
+int32_t gCenterHoriz;
+int32_t gDeliriumBlur;
+
+int32_t CONFIG_FunctionNameToNum(const char *func)
+{
+ int32_t i;
+
+ if (!func)
+ return -1;
+
+ i = hash_find(&h_gamefuncs,func);
+
+ if (i < 0)
+ {
+ char *str = Bstrtolower(Xstrdup(func));
+ i = hash_find(&h_gamefuncs,str);
+ Bfree(str);
+
+ return i;
+ }
+
+ return i;
+}
+
+
+char *CONFIG_FunctionNumToName(int32_t func)
+{
+ if ((unsigned)func >= (unsigned)NUMGAMEFUNCTIONS)
+ return NULL;
+ return gamefunctions[func];
+}
+
+
+int32_t CONFIG_AnalogNameToNum(const char *func)
+{
+ if (!func)
+ return -1;
+
+ if (!Bstrcasecmp(func,"analog_turning"))
+ {
+ return analog_turning;
+ }
+ if (!Bstrcasecmp(func,"analog_strafing"))
+ {
+ return analog_strafing;
+ }
+ if (!Bstrcasecmp(func,"analog_moving"))
+ {
+ return analog_moving;
+ }
+ if (!Bstrcasecmp(func,"analog_lookingupanddown"))
+ {
+ return analog_lookingupanddown;
+ }
+
+ return -1;
+}
+
+
+const char *CONFIG_AnalogNumToName(int32_t func)
+{
+ switch (func)
+ {
+ case analog_turning:
+ return "analog_turning";
+ case analog_strafing:
+ return "analog_strafing";
+ case analog_moving:
+ return "analog_moving";
+ case analog_lookingupanddown:
+ return "analog_lookingupanddown";
+ }
+
+ return NULL;
+}
+
+
+void CONFIG_SetDefaultKeys(const char (*keyptr)[MAXGAMEFUNCLEN], bool lazy/*=false*/)
+{
+ static char const s_gamefunc_[] = "gamefunc_";
+ int constexpr strlen_gamefunc_ = ARRAY_SIZE(s_gamefunc_) - 1;
+
+ if (!lazy)
+ {
+ Bmemset(KeyboardKeys, 0xff, sizeof(KeyboardKeys));
+ CONTROL_ClearAllBinds();
+ }
+
+ for (int i=0; i < ARRAY_SSIZE(gamefunctions); ++i)
+ {
+ if (gamefunctions[i][0] == '\0')
+ continue;
+
+ auto &key = KeyboardKeys[i];
+
+ int const default0 = KB_StringToScanCode(keyptr[i<<1]);
+ int const default1 = KB_StringToScanCode(keyptr[(i<<1)+1]);
+
+ // skip the function if the default key is already used
+ // or the function is assigned to another key
+ if (lazy && (key[0] != 0xff || (CONTROL_KeyIsBound(default0) && Bstrlen(CONTROL_KeyBinds[default0].cmdstr) > strlen_gamefunc_
+ && CONFIG_FunctionNameToNum(CONTROL_KeyBinds[default0].cmdstr + strlen_gamefunc_) >= 0)))
+ {
+#if 0 // defined(DEBUGGINGAIDS)
+ if (key[0] != 0xff)
+ initprintf("Skipping %s bound to %s\n", keyptr[i<<1], CONTROL_KeyBinds[default0].cmdstr);
+#endif
+ continue;
+ }
+
+ key[0] = default0;
+ key[1] = default1;
+
+ if (key[0])
+ CONTROL_FreeKeyBind(key[0]);
+
+ if (key[1])
+ CONTROL_FreeKeyBind(key[1]);
+
+ if (i == gamefunc_Show_Console)
+ OSD_CaptureKey(key[0]);
+ else
+ CONFIG_MapKey(i, key[0], 0, key[1], 0);
+ }
+}
+
+
+void CONFIG_SetDefaults(void)
+{
+ scripthandle = -1;
+
+#ifdef __ANDROID__
+ droidinput.forward_sens = 5.f;
+ droidinput.gameControlsAlpha = 0.5;
+ droidinput.hideStick = 0;
+ droidinput.pitch_sens = 5.f;
+ droidinput.quickSelectWeapon = 1;
+ droidinput.strafe_sens = 5.f;
+ droidinput.toggleCrouch = 1;
+ droidinput.yaw_sens = 5.f;
+
+ gSetup.xdim = droidinfo.screen_width;
+ gSetup.ydim = droidinfo.screen_height;
+#else
+# if defined RENDERTYPESDL && SDL_MAJOR_VERSION > 1
+ uint32_t inited = SDL_WasInit(SDL_INIT_VIDEO);
+ if (inited == 0)
+ SDL_Init(SDL_INIT_VIDEO);
+ else if (!(inited & SDL_INIT_VIDEO))
+ SDL_InitSubSystem(SDL_INIT_VIDEO);
+
+ SDL_DisplayMode dm;
+ if (SDL_GetDesktopDisplayMode(0, &dm) == 0)
+ {
+ gSetup.xdim = dm.w;
+ gSetup.ydim = dm.h;
+ }
+ else
+# endif
+ {
+ gSetup.xdim = 1024;
+ gSetup.ydim = 768;
+ }
+#endif
+
+#ifdef USE_OPENGL
+ gSetup.bpp = 32;
+#else
+ gSetup.bpp = 8;
+#endif
+
+#if defined(_WIN32)
+ MixRate = 44100;
+#elif defined __ANDROID__
+ MixRate = droidinfo.audio_sample_rate;
+#else
+ MixRate = 48000;
+#endif
+
+#if defined GEKKO || defined __OPENDINGUX__
+ NumVoices = 32;
+#else
+ NumVoices = 64;
+#endif
+
+#ifdef GEKKO
+ gSetup.usejoystick = 1;
+#else
+ gSetup.usejoystick = 0;
+#endif
+
+ gSetup.forcesetup = 1;
+ gSetup.noautoload = 1;
+ gSetup.fullscreen = 1;
+ gSetup.usemouse = 1;
+
+ //ud.config.AmbienceToggle = 1;
+ //ud.config.AutoAim = 1;
+ CheckForUpdates = 1;
+ FXVolume = 255;
+ MouseBias = 0;
+ MouseDeadZone = 0;
+ MusicToggle = 1;
+ MusicRestartsOnLoadToggle = 0;
+ MusicVolume = 195;
+ NumBits = 16;
+ NumChannels = 2;
+ ReverseStereo = 0;
+ gBrightness = 8;
+ //ud.config.ShowWeapons = 0;
+ SmoothInput = 1;
+ SoundToggle = 1;
+ CDAudioToggle = 0;
+ MusicDevice = 0;
+ //ud.config.VoiceToggle = 5; // bitfield, 1 = local, 2 = dummy, 4 = other players in DM
+ useprecache = 1;
+ configversion = 0;
+ //ud.crosshair = 1;
+ //ud.crosshairscale = 50;
+ //ud.default_skill = 1;
+ //ud.democams = 1;
+ gUpscaleFactor = 0;
+ //ud.display_bonus_screen = 1;
+ //ud.drawweapon = 1;
+ //ud.hudontop = 0;
+ //ud.idplayers = 1;
+ gLevelStats = 0;
+ gPowerupDuration = 1;
+ gShowMapTitle = 1;
+ //ud.lockout = 0;
+ //ud.m_marker = 1;
+ //ud.maxautosaves = 5;
+ //ud.menu_scrollbartilenum = -1;
+ //ud.menu_scrollbarz = 65536;
+ //ud.menu_scrollcursorz = 65536;
+ //ud.menu_slidebarmargin = 65536;
+ //ud.menu_slidebarz = 65536;
+ //ud.menu_slidecursorz = 65536;
+ //ud.menubackground = 1;
+ //ud.mouseaiming = 0;
+ //ud.mouseflip = 1;
+ //ud.msgdisptime = 120;
+ //ud.obituaries = 1;
+ //ud.pwlockout[0] = '\0';
+ gRunKeyMode = 0;
+ //ud.screen_size = 4;
+ //ud.screen_tilting = 1;
+ //ud.screenfade = 1;
+ //ud.shadow_pal = 4;
+ //ud.shadows = 1;
+ //ud.show_level_text = 1;
+ //ud.slidebar_paldisabled = 1;
+ //ud.statusbarflags = STATUSBAR_NOSHRINK;
+ //ud.statusbarmode = 1;
+ //ud.statusbarscale = 100;
+ //ud.team = 0;
+ //ud.textscale = 200;
+ //ud.viewbob = 1;
+ //ud.weaponscale = 100;
+ //ud.weaponsway = 1;
+ //ud.weaponswitch = 3; // new+empty
+ gFov = 90;
+ gCenterHoriz = 0;
+ gDeliriumBlur = 1;
+ gViewSize = 2;
+ gTurnSpeed = 92;
+ gDetail = 4;
+ gAutoRun = 0;
+ gViewInterpolate = 1;
+ gViewHBobbing = 1;
+ gViewVBobbing = 1;
+ gFollowMap = 1;
+ gOverlayMap = 0;
+ gRotateMap = 0;
+ gAimReticle = 0;
+ gSlopeTilting = 0;
+ gMessageState = 1;
+ gMessageCount = 4;
+ gMessageTime = 5;
+ gMessageFont = 0;
+ gbAdultContent = 0;
+ gDoppler = 1;
+ gShowWeapon = 0;
+ gzAdultPassword[0] = 0;
+
+ gMouseAimingFlipped = 0;
+ gMouseAim = 1;
+ gAutoAim = 1;
+ gWeaponSwitch = 1;
+
+ Bstrcpy(szPlayerName, "Player");
+
+ Bstrcpy(CommbatMacro[0], "I love the smell of napalm...");
+ Bstrcpy(CommbatMacro[1], "Is that gasoline I smell?");
+ Bstrcpy(CommbatMacro[2], "Ta da!");
+ Bstrcpy(CommbatMacro[3], "Who wants some, huh? Who's next?");
+ Bstrcpy(CommbatMacro[4], "I have something for you.");
+ Bstrcpy(CommbatMacro[5], "You just gonna stand there...");
+ Bstrcpy(CommbatMacro[6], "That'll teach ya!");
+ Bstrcpy(CommbatMacro[7], "Ooh, that wasn't a bit nice.");
+ Bstrcpy(CommbatMacro[8], "Amateurs!");
+ Bstrcpy(CommbatMacro[9], "Fool! You are already dead.");
+
+ CONFIG_SetDefaultKeys(keydefaults);
+
+ memset(MouseFunctions, -1, sizeof(MouseFunctions));
+ memset(MouseDigitalFunctions, -1, sizeof(MouseDigitalFunctions));
+ memset(JoystickFunctions, -1, sizeof(JoystickFunctions));
+ memset(JoystickDigitalFunctions, -1, sizeof(JoystickDigitalFunctions));
+
+ CONTROL_MouseSensitivity = DEFAULTMOUSESENSITIVITY;
+
+ for (int i=0; i=4) continue;
+ MouseFunctions[i][1] = CONFIG_FunctionNameToNum(mouseclickeddefaults[i]);
+ CONTROL_MapButton(MouseFunctions[i][1], i, 1, controldevice_mouse);
+ }
+
+ for (int i=0; i=0; i--)
+ {
+ if (KeyboardKeys[i][0] == keys[k] || KeyboardKeys[i][1] == keys[k])
+ {
+ Bsprintf(buf, "gamefunc_%s; ", CONFIG_FunctionNumToName(i));
+ Bstrcat(tempbuf,buf);
+ }
+ }
+
+ int const len = Bstrlen(tempbuf);
+
+ if (len >= 2)
+ {
+ tempbuf[len-2] = 0; // cut off the trailing "; "
+ CONTROL_BindKey(keys[k], tempbuf, 1, sctokeylut[match].key ? sctokeylut[match].key : ">");
+ }
+ else
+ {
+ CONTROL_FreeKeyBind(keys[k]);
+ }
+ }
+}
+
+
+void CONFIG_SetupMouse(void)
+{
+ if (scripthandle < 0)
+ return;
+
+ char str[80];
+ char temp[80];
+
+ for (int i=0; i 10)
+ tempbuf[Bstrlen(tempbuf) - 1] = '\0';
+
+ Bstrncpyz(szPlayerName, tempbuf, sizeof(szPlayerName));
+
+ //SCRIPT_GetString(scripthandle, "Comm Setup","RTSName",&ud.rtsname[0]);
+
+ SCRIPT_GetNumber(scripthandle, "Setup", "ConfigVersion", &configversion);
+ SCRIPT_GetNumber(scripthandle, "Setup", "ForceSetup", &gSetup.forcesetup);
+ SCRIPT_GetNumber(scripthandle, "Setup", "NoAutoLoad", &gSetup.noautoload);
+
+ int32_t cachesize;
+ SCRIPT_GetNumber(scripthandle, "Setup", "CacheSize", &cachesize);
+
+ if (cachesize > MAXCACHE1DSIZE)
+ MAXCACHE1DSIZE = cachesize;
+
+ if (gNoSetup == 0 && g_modDir[0] == '/')
+ {
+ struct Bstat st;
+ SCRIPT_GetString(scripthandle, "Setup","ModDir",&g_modDir[0]);
+
+ if (Bstat(g_modDir, &st))
+ {
+ if ((st.st_mode & S_IFDIR) != S_IFDIR)
+ {
+ initprintf("Invalid mod dir in cfg!\n");
+ Bsprintf(g_modDir,"/");
+ }
+ }
+ }
+
+ //if (g_grpNamePtr == NULL && g_addonNum == 0)
+ //{
+ // SCRIPT_GetStringPtr(scripthandle, "Setup", "SelectedGRP", &g_grpNamePtr);
+ // if (g_grpNamePtr && !Bstrlen(g_grpNamePtr))
+ // g_grpNamePtr = dup_filename(G_DefaultGrpFile());
+ //}
+ //
+ //if (!NAM_WW2GI)
+ //{
+ // SCRIPT_GetNumber(scripthandle, "Screen Setup", "Out", &ud.lockout);
+ // SCRIPT_GetString(scripthandle, "Screen Setup", "Password", &ud.pwlockout[0]);
+ //}
+
+ windowx = -1;
+ windowy = -1;
+
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "MaxRefreshFreq", (int32_t *)&maxrefreshfreq);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenBPP", &gSetup.bpp);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenHeight", &gSetup.ydim);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenMode", &gSetup.fullscreen);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "ScreenWidth", &gSetup.xdim);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPosX", (int32_t *)&windowx);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPosY", (int32_t *)&windowy);
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "WindowPositioning", (int32_t *)&windowpos);
+
+ if (gSetup.bpp < 8) gSetup.bpp = 32;
+
+#ifdef POLYMER
+ int32_t rendmode = 0;
+ SCRIPT_GetNumber(scripthandle, "Screen Setup", "Polymer", &rendmode);
+ glrendmode = (rendmode > 0) ? REND_POLYMER : REND_POLYMOST;
+#endif
+
+ //SCRIPT_GetNumber(scripthandle, "Misc", "Executions", &ud.executions);
+
+#ifdef _WIN32
+ SCRIPT_GetNumber(scripthandle, "Updates", "CheckForUpdates", &CheckForUpdates);
+ SCRIPT_GetNumber(scripthandle, "Updates", "LastUpdateCheck", &LastUpdateCheck);
+#endif
+
+ setupread = 1;
+ return 0;
+}
+
+
+void CONFIG_WriteSettings(void) // save binds and aliases to _settings.cfg
+{
+ char *ptr = Xstrdup(SetupFilename);
+ char filename[BMAX_PATH];
+
+ if (!Bstrcmp(SetupFilename, SETUPFILENAME))
+ Bsprintf(filename, "settings.cfg");
+ else
+ Bsprintf(filename, "%s_settings.cfg", strtok(ptr, "."));
+
+ BFILE *fp = Bfopen(filename, "wt");
+
+ if (fp)
+ {
+ Bfprintf(fp,"// this file is automatically generated by %s\n", AppProperName);
+ Bfprintf(fp,"unbindall\n");
+
+ for (int i=0; i= (MAXMOUSEBUTTONS-2)) continue;
+
+ if (CONFIG_FunctionNumToName(MouseFunctions[i][1]))
+ {
+ Bsprintf(buf, "MouseButtonClicked%d", i);
+ SCRIPT_PutString(scripthandle, "Controls", buf, CONFIG_FunctionNumToName(MouseFunctions[i][1]));
+ }
+ }
+
+ for (int i=0; i 0)
+ info.dpitch = max(info.dpitch - MouseDeadZone, 0);
+ else if (info.dpitch < 0)
+ info.dpitch = min(info.dpitch + MouseDeadZone, 0);
+
+ if (info.dyaw > 0)
+ info.dyaw = max(info.dyaw - MouseDeadZone, 0);
+ else if (info.dyaw < 0)
+ info.dyaw = min(info.dyaw + MouseDeadZone, 0);
+ }
+
+ if (MouseBias)
+ {
+ if (klabs(info.dyaw) > klabs(info.dpitch))
+ info.dpitch = tabledivide32_noinline(info.dpitch, MouseBias);
+ else info.dyaw = tabledivide32_noinline(info.dyaw, MouseBias);
+ }
+
+ if (gQuitRequest)
+ gInput.keyFlags.quit = 1;
+
+ if (gGameStarted && gInputMode != INPUT_MODE_2 && gInputMode != INPUT_MODE_1
+ && BUTTON(gamefunc_Send_Message))
+ {
+ CONTROL_ClearButton(gamefunc_Send_Message);
+ keyFlushScans();
+ gInputMode = INPUT_MODE_2;
+ }
+
+ if (BUTTON(gamefunc_AutoRun))
+ {
+ CONTROL_ClearButton(gamefunc_AutoRun);
+ gAutoRun = !gAutoRun;
+ if (gAutoRun)
+ viewSetMessage("Auto run ON");
+ else
+ viewSetMessage("Auto run OFF");
+ }
+
+ if (BUTTON(gamefunc_Map_Toggle))
+ {
+ CONTROL_ClearButton(gamefunc_Map_Toggle);
+ viewToggle(gViewMode);
+ }
+
+ if (BUTTON(gamefunc_Map_Follow_Mode))
+ {
+ CONTROL_ClearButton(gamefunc_Map_Follow_Mode);
+ gFollowMap = !gFollowMap;
+ gViewMap.FollowMode(gFollowMap);
+ }
+
+ if (BUTTON(gamefunc_Shrink_Screen))
+ {
+ if (gViewMode == 3)
+ {
+ CONTROL_ClearButton(gamefunc_Shrink_Screen);
+ viewResizeView(gViewSize + 1);
+ }
+ if (gViewMode == 2 || gViewMode == 4)
+ {
+ gZoom = ClipLow(gZoom - (gZoom >> 4), 64);
+ gViewMap.nZoom = gZoom;
+ }
+ }
+
+ if (BUTTON(gamefunc_Enlarge_Screen))
+ {
+ if (gViewMode == 3)
+ {
+ CONTROL_ClearButton(gamefunc_Enlarge_Screen);
+ viewResizeView(gViewSize - 1);
+ }
+ if (gViewMode == 2 || gViewMode == 4)
+ {
+ gZoom = ClipHigh(gZoom + (gZoom >> 4), 4096);
+ gViewMap.nZoom = gZoom;
+ }
+ }
+
+ if (BUTTON(gamefunc_Toggle_Crosshair))
+ {
+ CONTROL_ClearButton(gamefunc_Toggle_Crosshair);
+ gAimReticle = !gAimReticle;
+ }
+
+ if (BUTTON(gamefunc_Next_Weapon))
+ {
+ CONTROL_ClearButton(gamefunc_Next_Weapon);
+ gInput.keyFlags.nextWeapon = 1;
+ }
+
+ if (BUTTON(gamefunc_Previous_Weapon))
+ {
+ CONTROL_ClearButton(gamefunc_Previous_Weapon);
+ gInput.keyFlags.prevWeapon = 1;
+ }
+
+ if (BUTTON(gamefunc_Show_Opponents_Weapon))
+ {
+ CONTROL_ClearButton(gamefunc_Show_Opponents_Weapon);
+ gShowWeapon = !gShowWeapon;
+ }
+
+ if (BUTTON(gamefunc_Jump))
+ gInput.buttonFlags.jump = 1;
+
+ if (BUTTON(gamefunc_Crouch))
+ gInput.buttonFlags.crouch = 1;
+
+ if (BUTTON(gamefunc_Weapon_Fire))
+ gInput.buttonFlags.shoot = 1;
+
+ if (BUTTON(gamefunc_Weapon_Special_Fire))
+ gInput.buttonFlags.shoot2 = 1;
+
+ if (BUTTON(gamefunc_Open))
+ {
+ CONTROL_ClearButton(gamefunc_Open);
+ gInput.keyFlags.action = 1;
+ }
+
+ gInput.buttonFlags.lookUp = BUTTON(gamefunc_Look_Up);
+ gInput.buttonFlags.lookDown = BUTTON(gamefunc_Look_Down);
+
+ if (gInput.buttonFlags.lookUp || gInput.buttonFlags.lookDown)
+ gInput.keyFlags.lookCenter = 1;
+ else
+ {
+ gInput.buttonFlags.lookUp = BUTTON(gamefunc_Aim_Up);
+ gInput.buttonFlags.lookDown = BUTTON(gamefunc_Aim_Down);
+ }
+
+ if (BUTTON(gamefunc_Aim_Center))
+ {
+ CONTROL_ClearButton(gamefunc_Aim_Center);
+ gInput.keyFlags.lookCenter = 1;
+ }
+
+ gInput.keyFlags.spin180 = BUTTON(gamefunc_Turn_Around);
+
+ if (BUTTON(gamefunc_Inventory_Left))
+ {
+ CONTROL_ClearButton(gamefunc_Inventory_Left);
+ gInput.keyFlags.prevItem = 1;
+ }
+
+ if (BUTTON(gamefunc_Inventory_Right))
+ {
+ CONTROL_ClearButton(gamefunc_Inventory_Right);
+ gInput.keyFlags.nextItem = 1;
+ }
+
+ if (BUTTON(gamefunc_Inventory_Use))
+ {
+ CONTROL_ClearButton(gamefunc_Inventory_Use);
+ gInput.keyFlags.useItem = 1;
+ }
+
+ if (BUTTON(gamefunc_BeastVision))
+ {
+ CONTROL_ClearButton(gamefunc_BeastVision);
+ gInput.useFlags.useBeastVision = 1;
+ }
+
+ if (BUTTON(gamefunc_CrystalBall))
+ {
+ CONTROL_ClearButton(gamefunc_CrystalBall);
+ gInput.useFlags.useCrystalBall = 1;
+ }
+
+ if (BUTTON(gamefunc_JumpBoots))
+ {
+ CONTROL_ClearButton(gamefunc_JumpBoots);
+ gInput.useFlags.useJumpBoots = 1;
+ }
+
+ if (BUTTON(gamefunc_MedKit))
+ {
+ CONTROL_ClearButton(gamefunc_MedKit);
+ gInput.useFlags.useMedKit = 1;
+ }
+
+ for (int i = 0; i < 10; i++)
+ {
+ if (BUTTON(gamefunc_Weapon_1 + i))
+ {
+ CONTROL_ClearButton(gamefunc_Weapon_1 + i);
+ gInput.newWeapon = 1 + i;
+ }
+ }
+
+ if (BUTTON(gamefunc_ProximityBombs))
+ {
+ CONTROL_ClearButton(gamefunc_ProximityBombs);
+ gInput.newWeapon = 11;
+ }
+
+ if (BUTTON(gamefunc_RemoteBombs))
+ {
+ CONTROL_ClearButton(gamefunc_RemoteBombs);
+ gInput.newWeapon = 12;
+ }
+
+ if (BUTTON(gamefunc_Holster_Weapon))
+ {
+ CONTROL_ClearButton(gamefunc_Holster_Weapon);
+ gInput.keyFlags.holsterWeapon = 1;
+ }
+
+ char run = gRunKeyMode ? (BUTTON(gamefunc_Run) | gAutoRun) : (BUTTON(gamefunc_Run) ^ gAutoRun);
+ char run2 = BUTTON(gamefunc_Run);
+
+ gInput.syncFlags.run = run;
+
+ if (BUTTON(gamefunc_Move_Forward))
+ forward += (1+run)<<10;
+
+ if (BUTTON(gamefunc_Move_Backward))
+ forward -= (1+run)<<10;
+
+ char turnLeft = 0, turnRight = 0;
+
+ if (BUTTON(gamefunc_Strafe))
+ {
+ if (BUTTON(gamefunc_Turn_Left))
+ strafe += (1 + run) << 10;
+ if (BUTTON(gamefunc_Turn_Right))
+ strafe -= (1 + run) << 10;
+ }
+ else
+ {
+ if (BUTTON(gamefunc_Strafe_Left))
+ strafe += (1 + run) << 10;
+ if (BUTTON(gamefunc_Strafe_Right))
+ strafe -= (1 + run) << 10;
+ if (BUTTON(gamefunc_Turn_Left))
+ turnLeft = 1;
+ if (BUTTON(gamefunc_Turn_Right))
+ turnRight = 1;
+ }
+
+ if (turnLeft || turnRight)
+ iTurnCount += 4;
+ else
+ iTurnCount = 0;
+
+ if (turnLeft)
+ turn -= fix16_from_int(ClipHigh(12 * iTurnCount, gTurnSpeed))>>2;
+ if (turnRight)
+ turn += fix16_from_int(ClipHigh(12 * iTurnCount, gTurnSpeed))>>2;
+
+ if ((run2 || run) && iTurnCount > 24)
+ turn <<= 1;
+
+ if (BUTTON(gamefunc_Strafe))
+ strafe = ClipRange(strafe - info.dyaw, -2048, 2048);
+ else
+ turn = fix16_clamp(turn + fix16_div(fix16_from_int(info.dyaw), F16(32)), F16(-1024)>>2, F16(1024)>>2);
+
+ strafe = ClipRange(strafe-(info.dx<<5), -2048, 2048);
+
+#if 0
+ if (info.dz < 0)
+ gInput.mlook = ClipRange((info.dz+127)>>7, -127, 127);
+ else
+ gInput.mlook = ClipRange(info.dz>>7, -127, 127);
+#endif
+ gInput.q16mlook = fix16_clamp(fix16_div(fix16_from_int(info.dpitch), F16(256)), F16(-127)>>2, F16(127)>>2);
+ if (!gMouseAimingFlipped)
+ gInput.q16mlook = -gInput.q16mlook;
+ forward = ClipRange(forward - info.dz, -2048, 2048);
+
+ if (KB_KeyPressed(sc_Pause)) // 0xc5 in disassembly
+ {
+ gInput.keyFlags.pause = 1;
+ KB_ClearKeyDown(sc_Pause);
+ }
+
+ if (!gViewMap.bFollowMode && gViewMode == 4)
+ {
+ gViewMap.turn = fix16_to_int(turn<<2);
+ gViewMap.forward = forward>>8;
+ gViewMap.strafe = strafe>>8;
+ turn = 0;
+ forward = 0;
+ strafe = 0;
+ }
+ gInput.forward = forward;
+ gInput.q16turn = turn;
+ gInput.strafe = strafe;
+}
diff --git a/source/blood/src/controls.h b/source/blood/src/controls.h
new file mode 100644
index 000000000..94f008f86
--- /dev/null
+++ b/source/blood/src/controls.h
@@ -0,0 +1,112 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#pragma pack(push, 1)
+
+union BUTTONFLAGS
+{
+ int8_t byte;
+ struct
+ {
+ unsigned int jump : 1;
+ unsigned int crouch : 1;
+ unsigned int shoot : 1;
+ unsigned int shoot2 : 1;
+ unsigned int lookUp : 1;
+ unsigned int lookDown : 1;
+ };
+};
+
+union KEYFLAGS
+{
+ int16_t word;
+ struct
+ {
+ unsigned int action : 1;
+ unsigned int jab : 1;
+ unsigned int prevItem : 1;
+ unsigned int nextItem : 1;
+ unsigned int useItem : 1;
+ unsigned int prevWeapon : 1;
+ unsigned int nextWeapon : 1;
+ unsigned int holsterWeapon : 1;
+ unsigned int lookCenter : 1;
+ unsigned int lookLeft : 1;
+ unsigned int lookRight : 1;
+ unsigned int spin180 : 1;
+ unsigned int pause : 1;
+ unsigned int quit : 1;
+ unsigned int restart : 1;
+ };
+};
+
+union USEFLAGS
+{
+ uint8_t byte;
+ struct
+ {
+ unsigned int useBeastVision : 1;
+ unsigned int useCrystalBall : 1;
+ unsigned int useJumpBoots : 1;
+ unsigned int useMedKit : 1;
+ };
+};
+
+union SYNCFLAGS
+{
+ uint8_t byte;
+ struct
+ {
+ unsigned int buttonChange : 1;
+ unsigned int keyChange : 1;
+ unsigned int useChange : 1;
+ unsigned int weaponChange : 1;
+ unsigned int mlookChange : 1;
+ unsigned int run : 1;
+ };
+};
+struct GINPUT
+{
+ SYNCFLAGS syncFlags;
+ int16_t forward;
+ fix16_t q16turn;
+ int16_t strafe;
+ BUTTONFLAGS buttonFlags;
+ KEYFLAGS keyFlags;
+ USEFLAGS useFlags;
+ uint8_t newWeapon;
+ fix16_t q16mlook;
+};
+
+#pragma pack(pop)
+
+extern GINPUT gInput;
+extern bool bSilentAim;
+extern int32_t gMouseAim; // Should be an int32 due to being passed to OSD
+
+int32_t ctrlCheckAllInput(void);
+void ctrlClearAllInput(void);
+void ctrlInit();
+void ctrlGetInput();
+
diff --git a/source/blood/src/credits.cpp b/source/blood/src/credits.cpp
new file mode 100644
index 000000000..a88abcdb1
--- /dev/null
+++ b/source/blood/src/credits.cpp
@@ -0,0 +1,284 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#include "SmackerDecoder.h"
+#include "fx_man.h"
+#include "keyboard.h"
+#include "common_game.h"
+#include "blood.h"
+#include "config.h"
+#include "controls.h"
+#include "globals.h"
+#include "resource.h"
+#include "screen.h"
+#include "sound.h"
+#include "view.h"
+
+char exitCredits = 0;
+
+char Wait(int nTicks)
+{
+ gGameClock = 0;
+ while (gGameClock < nTicks)
+ {
+ timerUpdate();
+ char key = keyGetScan();
+ if (key)
+ {
+ if (key == sc_Escape) // sc_Escape
+ exitCredits = 1;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+char DoFade(char r, char g, char b, int nTicks)
+{
+ dassert(nTicks > 0);
+ scrSetupFade(r, g, b);
+ gGameClock = gFrameClock = 0;
+ do
+ {
+ while (gGameClock < gFrameClock) { timerUpdate();};
+ gFrameClock += 2;
+ scrNextPage();
+ scrFadeAmount(divscale16(ClipHigh(gGameClock, nTicks), nTicks));
+ if (keyGetScan())
+ return 0;
+ } while (gGameClock <= nTicks);
+ return 1;
+}
+
+char DoUnFade(int nTicks)
+{
+ dassert(nTicks > 0);
+ scrSetupUnfade();
+ gGameClock = gFrameClock = 0;
+ do
+ {
+ while (gGameClock < gFrameClock) { timerUpdate(); };
+ scrNextPage();
+ scrFadeAmount(0x10000-divscale16(ClipHigh(gGameClock, nTicks), nTicks));
+ if (keyGetScan())
+ return 0;
+ } while (gGameClock <= nTicks);
+ return 1;
+}
+
+void credLogosDos(void)
+{
+ char bShift = keystatus[sc_LeftShift] | keystatus[sc_RightShift];
+ videoSetViewableArea(0, 0, xdim-1, ydim-1);
+ DoUnFade(1);
+ videoClearScreen(0);
+ if (bShift)
+ return;
+ {
+ //CSMKPlayer smkPlayer;
+ //if (smkPlayer.PlaySMKWithWAV("LOGO.SMK", 300) == 1)
+ //{
+ rotatesprite(160<<16, 100<<16, 65536, 0, 2050, 0, 0, 0x4a, 0, 0, xdim-1, ydim-1);
+ sndStartSample("THUNDER2", 128, -1);
+ scrNextPage();
+ if (!Wait(360))
+ return;
+ if (!DoFade(0, 0, 0, 60))
+ return;
+ //}
+ //if (smkPlayer.PlaySMKWithWAV("GTI.SMK", 301) == 1)
+ //{
+ videoClearScreen(0);
+ rotatesprite(160<<16, 100<<16, 65536, 0, 2052, 0, 0, 0x0a, 0, 0, xdim-1, ydim-1);
+ scrNextPage();
+ DoUnFade(1);
+ sndStartSample("THUNDER2", 128, -1);
+ if (!Wait(360))
+ return;
+ //}
+ }
+ sndPlaySpecialMusicOrNothing(MUS_INTRO);
+ sndStartSample("THUNDER2", 128, -1);
+ if (!DoFade(0, 0, 0, 60))
+ return;
+ videoClearScreen(0);
+ scrNextPage();
+ if (!DoUnFade(1))
+ return;
+ videoClearScreen(0);
+ rotatesprite(160<<16, 100<<16, 65536, 0, 2518, 0, 0, 0x4a, 0, 0, xdim-1, ydim-1);
+ scrNextPage();
+ Wait(360);
+ sndFadeSong(4000);
+}
+
+void credReset(void)
+{
+ videoClearScreen(0);
+ scrNextPage();
+ DoFade(0,0,0,1);
+ scrSetupUnfade();
+ DoUnFade(1);
+}
+
+int credKOpen4Load(char *&pzFile)
+{
+ int nLen = strlen(pzFile);
+ for (int i = 0; i < nLen; i++)
+ {
+ if (pzFile[i] == '\\')
+ pzFile[i] = '/';
+ }
+ int nHandle = kopen4loadfrommod(pzFile, 0);
+ if (nHandle == -1)
+ {
+ // Hack
+ if (nLen >= 3 && isalpha(pzFile[0]) && pzFile[1] == ':' && pzFile[2] == '/')
+ {
+ pzFile += 3;
+ nHandle = kopen4loadfrommod(pzFile, 0);
+ }
+ }
+ return nHandle;
+}
+
+#define kSMKPal 5
+#define kSMKTile (MAXTILES-1)
+
+void credPlaySmk(const char *_pzSMK, const char *_pzWAV, int nWav)
+{
+#if 0
+ CSMKPlayer smkPlayer;
+ if (dword_148E14 >= 0)
+ {
+ if (toupper(*pzSMK) == 'A'+dword_148E14)
+ {
+ if (Redbook.sub_82258() == 0 || Redbook.sub_82258() > 20)
+ return;
+ }
+ Redbook.sub_82554();
+ }
+ smkPlayer.sub_82E6C(pzSMK, pzWAV);
+#endif
+ if (Bstrlen(_pzSMK) == 0)
+ return;
+ char *pzSMK = Xstrdup(_pzSMK);
+ char *pzWAV = Xstrdup(_pzWAV);
+ char *pzSMK_ = pzSMK;
+ char *pzWAV_ = pzWAV;
+ int nHandleSMK = credKOpen4Load(pzSMK);
+ if (nHandleSMK == -1)
+ {
+ Bfree(pzSMK_);
+ Bfree(pzWAV_);
+ return;
+ }
+ kclose(nHandleSMK);
+ SmackerHandle hSMK = Smacker_Open(pzSMK);
+ if (!hSMK.isValid)
+ {
+ Bfree(pzSMK_);
+ Bfree(pzWAV_);
+ return;
+ }
+ uint32_t nWidth, nHeight;
+ Smacker_GetFrameSize(hSMK, nWidth, nHeight);
+ uint8_t palette[768];
+ uint8_t *pFrame = (uint8_t*)Xmalloc(nWidth*nHeight);
+ waloff[kSMKTile] = (intptr_t)pFrame;
+ tilesiz[kSMKTile].y = nWidth;
+ tilesiz[kSMKTile].x = nHeight;
+ if (!pFrame)
+ {
+ Smacker_Close(hSMK);
+ Bfree(pzSMK_);
+ Bfree(pzWAV_);
+ return;
+ }
+ int nFrameRate = Smacker_GetFrameRate(hSMK);
+ int nFrames = Smacker_GetNumFrames(hSMK);
+
+ Smacker_GetPalette(hSMK, palette);
+ paletteSetColorTable(kSMKPal, palette);
+ videoSetPalette(gBrightness>>2, kSMKPal, 8+2);
+
+ int nScale;
+
+ if ((nWidth / (nHeight * 1.2f)) > (1.f * xdim / ydim))
+ nScale = divscale16(320 * xdim * 3, nWidth * ydim * 4);
+ else
+ nScale = divscale16(200, nHeight);
+
+ if (nWav)
+ sndStartWavID(nWav, FXVolume);
+ else
+ {
+ int nHandleWAV = credKOpen4Load(pzWAV);
+ if (nHandleWAV != -1)
+ {
+ kclose(nHandleWAV);
+ sndStartWavDisk(pzWAV, FXVolume);
+ }
+ }
+
+ UpdateDacs(0, true);
+
+ timerUpdate();
+ int32_t nStartTime = totalclock;
+
+ ctrlClearAllInput();
+
+ int nFrame = 0;
+ do
+ {
+ G_HandleAsync();
+ if (scale(totalclock-nStartTime, nFrameRate, kTicRate) < nFrame)
+ continue;
+
+ if (ctrlCheckAllInput())
+ break;
+
+ videoClearScreen(0);
+ Smacker_GetPalette(hSMK, palette);
+ paletteSetColorTable(kSMKPal, palette);
+ videoSetPalette(gBrightness >> 2, kSMKPal, 0);
+ tileInvalidate(kSMKTile, 0, 1 << 4); // JBF 20031228
+ Smacker_GetFrame(hSMK, pFrame);
+ rotatesprite_fs(160<<16, 100<<16, nScale, 512, kSMKTile, 0, 0, 2|4|8|64);
+
+ videoNextPage();
+
+ ctrlClearAllInput();
+ nFrame++;
+ Smacker_GetNextFrame(hSMK);
+ } while(nFrame < nFrames);
+
+ Smacker_Close(hSMK);
+ ctrlClearAllInput();
+ FX_StopAllSounds();
+ videoSetPalette(gBrightness >> 2, 0, 8+2);
+ Bfree(pFrame);
+ Bfree(pzSMK_);
+ Bfree(pzWAV_);
+}
diff --git a/source/blood/src/credits.h b/source/blood/src/credits.h
new file mode 100644
index 000000000..3e1bbaaa9
--- /dev/null
+++ b/source/blood/src/credits.h
@@ -0,0 +1,27 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+void credLogosDos(void);
+void credReset(void);
+void credPlaySmk(const char *pzSMK, const char *pzWAV, int nWAV);
\ No newline at end of file
diff --git a/source/blood/src/db.cpp b/source/blood/src/db.cpp
new file mode 100644
index 000000000..97c9308f2
--- /dev/null
+++ b/source/blood/src/db.cpp
@@ -0,0 +1,1240 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#ifdef POLYMER
+#include "polymer.h"
+#endif
+#include "compat.h"
+#include "common_game.h"
+#include "crc32.h"
+
+#include "actor.h"
+#include "blood.h"
+#include "db.h"
+#include "iob.h"
+
+unsigned short gStatCount[kMaxStatus + 1];
+
+XSPRITE xsprite[kMaxXSprites];
+XSECTOR xsector[kMaxXSectors];
+XWALL xwall[kMaxXWalls];
+
+int xvel[kMaxSprites], yvel[kMaxSprites], zvel[kMaxSprites];
+
+int gVisibility;
+
+const char *gItemText[] = {
+ "Skull Key",
+ "Eye Key",
+ "Fire Key",
+ "Dagger Key",
+ "Spider Key",
+ "Moon Key",
+ "Key 7",
+ "Doctor's Bag",
+ "Medicine Pouch",
+ "Life Essence",
+ "Life Seed",
+ "Red Potion",
+ "Feather Fall",
+ "Limited Invisibility",
+ "INVULNERABILITY",
+ "Boots of Jumping",
+ "Raven Flight",
+ "Guns Akimbo",
+ "Diving Suit",
+ "Gas mask",
+ "Clone",
+ "Crystal Ball",
+ "Decoy",
+ "Doppleganger",
+ "Reflective shots",
+ "Beast Vision",
+ "ShadowCloak",
+ "Rage shroom",
+ "Delirium Shroom",
+ "Grow shroom",
+ "Shrink shroom",
+ "Death mask",
+ "Wine Goblet",
+ "Wine Bottle",
+ "Skull Grail",
+ "Silver Grail",
+ "Tome",
+ "Black Chest",
+ "Wooden Chest",
+ "Asbestos Armor",
+ "Basic Armor",
+ "Body Armor",
+ "Fire Armor",
+ "Spirit Armor",
+ "Super Armor",
+ "Blue Team Base",
+ "Red Team Base",
+ "Blue Flag",
+ "Red Flag",
+ "DUMMY",
+ "Level map",
+};
+
+const char *gAmmoText[] = {
+ "Spray can",
+ "Bundle of TNT*",
+ "Bundle of TNT",
+ "Case of TNT",
+ "Proximity Detonator",
+ "Remote Detonator",
+ "Trapped Soul",
+ "4 shotgun shells",
+ "Box of shotgun shells",
+ "A few bullets",
+ "Voodoo Doll",
+ "OBSOLETE",
+ "Full drum of bullets",
+ "Tesla Charge",
+ "OBSOLETE",
+ "OBSOLETE",
+ "Flares",
+ "OBSOLETE",
+ "OBSOLETE",
+ "Gasoline Can",
+ NULL,
+};
+
+const char *gWeaponText[] = {
+ "RANDOM",
+ "Sawed-off",
+ "Tommy Gun",
+ "Flare Pistol",
+ "Voodoo Doll",
+ "Tesla Cannon",
+ "Napalm Launcher",
+ "Pitchfork",
+ "Spray Can",
+ "Dynamite",
+ "Life Leech",
+};
+
+
+
+void dbCrypt(char *pPtr, int nLength, int nKey)
+{
+ for (int i = 0; i < nLength; i++)
+ {
+ pPtr[i] = pPtr[i] ^ nKey;
+ nKey++;
+ }
+}
+
+void InsertSpriteSect(int nSprite, int nSector)
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ int nOther = headspritesect[nSector];
+ if (nOther >= 0)
+ {
+ prevspritesect[nSprite] = prevspritesect[nOther];
+ nextspritesect[nSprite] = -1;
+ nextspritesect[prevspritesect[nOther]] = nSprite;
+ prevspritesect[nOther] = nSprite;
+ }
+ else
+ {
+ prevspritesect[nSprite] = nSprite;
+ nextspritesect[nSprite] = -1;
+ headspritesect[nSector] = nSprite;
+ }
+ sprite[nSprite].sectnum = nSector;
+}
+
+void RemoveSpriteSect(int nSprite)
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ int nSector = sprite[nSprite].sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ int nOther = nextspritesect[nSprite];
+ if (nOther < 0)
+ {
+ nOther = headspritesect[nSector];
+ }
+ prevspritesect[nOther] = prevspritesect[nSprite];
+ if (headspritesect[nSector] != nSprite)
+ {
+ nextspritesect[prevspritesect[nSprite]] = nextspritesect[nSprite];
+ }
+ else
+ {
+ headspritesect[nSector] = nextspritesect[nSprite];
+ }
+ sprite[nSprite].sectnum = -1;
+}
+
+void InsertSpriteStat(int nSprite, int nStat)
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ dassert(nStat >= 0 && nStat <= kMaxStatus);
+ int nOther = headspritestat[nStat];
+ if (nOther >= 0)
+ {
+ prevspritestat[nSprite] = prevspritestat[nOther];
+ nextspritestat[nSprite] = -1;
+ nextspritestat[prevspritestat[nOther]] = nSprite;
+ prevspritestat[nOther] = nSprite;
+ }
+ else
+ {
+ prevspritestat[nSprite] = nSprite;
+ nextspritestat[nSprite] = -1;
+ headspritestat[nStat] = nSprite;
+ }
+ sprite[nSprite].statnum = nStat;
+ gStatCount[nStat]++;
+}
+
+void RemoveSpriteStat(int nSprite)
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ int nStat = sprite[nSprite].statnum;
+ dassert(nStat >= 0 && nStat <= kMaxStatus);
+ int nOther = nextspritestat[nSprite];
+ if (nOther < 0)
+ {
+ nOther = headspritestat[nStat];
+ }
+ prevspritestat[nOther] = prevspritestat[nSprite];
+ if (headspritestat[nStat] != nSprite)
+ {
+ nextspritestat[prevspritestat[nSprite]] = nextspritestat[nSprite];
+ }
+ else
+ {
+ headspritestat[nStat] = nextspritestat[nSprite];
+ }
+ sprite[nSprite].statnum = -1;
+ gStatCount[nStat]--;
+}
+
+void qinitspritelists(void) // Replace
+{
+ for (short i = 0; i <= kMaxSectors; i++)
+ {
+ headspritesect[i] = -1;
+ }
+ for (short i = 0; i <= kMaxStatus; i++)
+ {
+ headspritestat[i] = -1;
+ }
+ for (short i = 0; i < kMaxSprites; i++)
+ {
+ sprite[i].sectnum = -1;
+ sprite[i].index = -1;
+ InsertSpriteStat(i, kMaxStatus);
+ }
+ memset(gStatCount, 0, sizeof(gStatCount));
+}
+
+int InsertSprite(int nSector, int nStat)
+{
+ int nSprite = headspritestat[kMaxStatus];
+ dassert(nSprite < kMaxSprites);
+ if (nSprite < 0)
+ {
+ return nSprite;
+ }
+ RemoveSpriteStat(nSprite);
+ spritetype *pSprite = &sprite[nSprite];
+ memset(&sprite[nSprite], 0, sizeof(spritetype));
+ InsertSpriteStat(nSprite, nStat);
+ InsertSpriteSect(nSprite, nSector);
+ pSprite->cstat = 128;
+ pSprite->clipdist = 32;
+ pSprite->xrepeat = pSprite->yrepeat = 64;
+ pSprite->owner = -1;
+ pSprite->extra = -1;
+ pSprite->index = nSprite;
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+
+ return nSprite;
+}
+
+int qinsertsprite(short nSector, short nStat) // Replace
+{
+ return InsertSprite(nSector, nStat);
+}
+
+int DeleteSprite(int nSprite)
+{
+ if (sprite[nSprite].extra > 0)
+ {
+ dbDeleteXSprite(sprite[nSprite].extra);
+ }
+ dassert(sprite[nSprite].statnum >= 0 && sprite[nSprite].statnum < kMaxStatus);
+ RemoveSpriteStat(nSprite);
+ dassert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
+ RemoveSpriteSect(nSprite);
+ InsertSpriteStat(nSprite, kMaxStatus);
+
+ return nSprite;
+}
+
+int qdeletesprite(short nSprite) // Replace
+{
+ return DeleteSprite(nSprite);
+}
+
+int ChangeSpriteSect(int nSprite, int nSector)
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ dassert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
+ RemoveSpriteSect(nSprite);
+ InsertSpriteSect(nSprite, nSector);
+ return 0;
+}
+
+int qchangespritesect(short nSprite, short nSector)
+{
+ return ChangeSpriteSect(nSprite, nSector);
+}
+
+int ChangeSpriteStat(int nSprite, int nStatus)
+{
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ dassert(nStatus >= 0 && nStatus < kMaxStatus);
+ dassert(sprite[nSprite].statnum >= 0 && sprite[nSprite].statnum < kMaxStatus);
+ dassert(sprite[nSprite].sectnum >= 0 && sprite[nSprite].sectnum < kMaxSectors);
+ RemoveSpriteStat(nSprite);
+ InsertSpriteStat(nSprite, nStatus);
+ return 0;
+}
+
+int qchangespritestat(short nSprite, short nStatus)
+{
+ return ChangeSpriteStat(nSprite, nStatus);
+}
+
+unsigned short nextXSprite[kMaxXSprites];
+unsigned short nextXWall[kMaxXWalls];
+unsigned short nextXSector[kMaxXSectors];
+
+void InitFreeList(unsigned short *pList, int nCount)
+{
+ for (int i = 1; i < nCount; i++)
+ {
+ pList[i] = i-1;
+ }
+ pList[0] = nCount - 1;
+}
+
+void InsertFree(unsigned short *pList, int nIndex)
+{
+ pList[nIndex] = pList[0];
+ pList[0] = nIndex;
+}
+
+unsigned short dbInsertXSprite(int nSprite)
+{
+ int nXSprite = nextXSprite[0];
+ nextXSprite[0] = nextXSprite[nXSprite];
+ if (nXSprite == 0)
+ {
+ ThrowError("Out of free XSprites");
+ }
+ memset(&xsprite[nXSprite], 0, sizeof(XSPRITE));
+ if (!bVanilla)
+ memset(&gSpriteHit[nXSprite], 0, sizeof(SPRITEHIT));
+ xsprite[nXSprite].reference = nSprite;
+ sprite[nSprite].extra = nXSprite;
+ return nXSprite;
+}
+
+void dbDeleteXSprite(int nXSprite)
+{
+ dassert(xsprite[nXSprite].reference >= 0);
+ dassert(sprite[xsprite[nXSprite].reference].extra == nXSprite);
+ InsertFree(nextXSprite, nXSprite);
+ sprite[xsprite[nXSprite].reference].extra = -1;
+ xsprite[nXSprite].reference = -1;
+}
+
+unsigned short dbInsertXWall(int nWall)
+{
+ int nXWall = nextXWall[0];
+ nextXWall[0] = nextXWall[nXWall];
+ if (nXWall == 0)
+ {
+ ThrowError("Out of free XWalls");
+ }
+ memset(&xwall[nXWall], 0, sizeof(XWALL));
+ xwall[nXWall].reference = nWall;
+ wall[nWall].extra = nXWall;
+ return nXWall;
+}
+
+void dbDeleteXWall(int nXWall)
+{
+ dassert(xwall[nXWall].reference >= 0);
+ InsertFree(nextXWall, nXWall);
+ wall[xwall[nXWall].reference].extra = -1;
+ xwall[nXWall].reference = -1;
+}
+
+unsigned short dbInsertXSector(int nSector)
+{
+ int nXSector = nextXSector[0];
+ nextXSector[0] = nextXSector[nXSector];
+ if (nXSector == 0)
+ {
+ ThrowError("Out of free XSectors");
+ }
+ memset(&xsector[nXSector], 0, sizeof(XSECTOR));
+ xsector[nXSector].reference = nSector;
+ sector[nSector].extra = nXSector;
+ return nXSector;
+}
+
+void dbDeleteXSector(int nXSector)
+{
+ dassert(xsector[nXSector].reference >= 0);
+ InsertFree(nextXSector, nXSector);
+ sector[xsector[nXSector].reference].extra = -1;
+ xsector[nXSector].reference = -1;
+}
+
+void dbXSpriteClean(void)
+{
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ int nXSprite = sprite[i].extra;
+ if (nXSprite == 0)
+ {
+ sprite[i].extra = -1;
+ }
+ if (sprite[i].statnum < kMaxStatus && nXSprite > 0)
+ {
+ dassert(nXSprite < kMaxXSprites);
+ if (xsprite[nXSprite].reference != i)
+ {
+ int nXSprite2 = dbInsertXSprite(i);
+ memcpy(&xsprite[nXSprite2], &xsprite[nXSprite], sizeof(XSPRITE));
+ xsprite[nXSprite2].reference = i;
+ }
+ }
+ }
+ for (int i = 1; i < kMaxXSprites; i++)
+ {
+ int nSprite = xsprite[i].reference;
+ if (nSprite >= 0)
+ {
+ dassert(nSprite < kMaxSprites);
+ if (sprite[nSprite].statnum >= kMaxStatus || sprite[nSprite].extra != i)
+ {
+ InsertFree(nextXSprite, i);
+ xsprite[i].reference = -1;
+ }
+ }
+ }
+}
+
+void dbXWallClean(void)
+{
+ for (int i = 0; i < numwalls; i++)
+ {
+ int nXWall = wall[i].extra;
+ if (nXWall == 0)
+ {
+ wall[i].extra = -1;
+ }
+ if (nXWall > 0)
+ {
+ dassert(nXWall < kMaxXWalls);
+ if (xwall[nXWall].reference == -1)
+ {
+ wall[i].extra = -1;
+ }
+ else
+ {
+ xwall[nXWall].reference = i;
+ }
+ }
+ }
+ for (int i = 0; i < numwalls; i++)
+ {
+ int nXWall = wall[i].extra;
+ if (nXWall > 0)
+ {
+ dassert(nXWall < kMaxXWalls);
+ if (xwall[nXWall].reference != i)
+ {
+ int nXWall2 = dbInsertXWall(i);
+ memcpy(&xwall[nXWall2], &xwall[nXWall], sizeof(XWALL));
+ xwall[nXWall2].reference = i;
+ }
+ }
+ }
+ for (int i = 1; i < kMaxXWalls; i++)
+ {
+ int nWall = xwall[i].reference;
+ if (nWall >= 0)
+ {
+ dassert(nWall < kMaxWalls);
+ if (nWall >= numwalls || wall[nWall].extra != i)
+ {
+ InsertFree(nextXWall, i);
+ xwall[i].reference = -1;
+ }
+ }
+ }
+}
+
+void dbXSectorClean(void)
+{
+ for (int i = 0; i < numsectors; i++)
+ {
+ int nXSector = sector[i].extra;
+ if (nXSector == 0)
+ {
+ sector[i].extra = -1;
+ }
+ if (nXSector > 0)
+ {
+ dassert(nXSector < kMaxXSectors);
+ if (xsector[nXSector].reference == -1)
+ {
+ sector[i].extra = -1;
+ }
+ else
+ {
+ xsector[nXSector].reference = i;
+ }
+ }
+ }
+ for (int i = 0; i < numsectors; i++)
+ {
+ int nXSector = sector[i].extra;
+ if (nXSector > 0)
+ {
+ dassert(nXSector < kMaxXSectors);
+ if (xsector[nXSector].reference != i)
+ {
+ int nXSector2 = dbInsertXSector(i);
+ memcpy(&xsector[nXSector2], &xsector[nXSector], sizeof(XSECTOR));
+ xsector[nXSector2].reference = i;
+ }
+ }
+ }
+ for (int i = 1; i < kMaxXSectors; i++)
+ {
+ int nSector = xsector[i].reference;
+ if (nSector >= 0)
+ {
+ dassert(nSector < kMaxSectors);
+ if (nSector >= numsectors || sector[nSector].extra != i)
+ {
+ InsertFree(nextXSector, i);
+ xsector[i].reference = -1;
+ }
+ }
+ }
+}
+
+void dbInit(void)
+{
+ InitFreeList(nextXSprite, kMaxXSprites);
+ for (int i = 1; i < kMaxXSprites; i++)
+ {
+ xsprite[i].reference = -1;
+ }
+ InitFreeList(nextXWall, kMaxXWalls);
+ for (int i = 1; i < kMaxXWalls; i++)
+ {
+ xwall[i].reference = -1;
+ }
+ InitFreeList(nextXSector, kMaxXSectors);
+ for (int i = 1; i < kMaxXSectors; i++)
+ {
+ xsector[i].reference = -1;
+ }
+ initspritelists();
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ sprite[i].cstat = 128;
+ }
+}
+
+void PropagateMarkerReferences(void)
+{
+ int nSprite, nNextSprite;
+ for (nSprite = headspritestat[10]; nSprite != -1; nSprite = nNextSprite)
+ {
+ nNextSprite = nextspritestat[nSprite];
+ switch (sprite[nSprite].type)
+ {
+ case 8:
+ {
+ int nOwner = sprite[nSprite].owner;
+ if (nOwner >= 0 && nOwner < numsectors)
+ {
+ int nXSector = sector[nOwner].extra;
+ if (nXSector > 0 && nXSector < kMaxXSectors)
+ {
+ xsector[nXSector].at2c_0 = nSprite;
+ continue;
+ }
+ }
+ break;
+ }
+ case 3:
+ {
+ int nOwner = sprite[nSprite].owner;
+ if (nOwner >= 0 && nOwner < numsectors)
+ {
+ int nXSector = sector[nOwner].extra;
+ if (nXSector > 0 && nXSector < kMaxXSectors)
+ {
+ xsector[nXSector].at2c_0 = nSprite;
+ continue;
+ }
+ }
+ break;
+ }
+ case 4:
+ {
+ int nOwner = sprite[nSprite].owner;
+ if (nOwner >= 0 && nOwner < numsectors)
+ {
+ int nXSector = sector[nOwner].extra;
+ if (nXSector > 0 && nXSector < kMaxXSectors)
+ {
+ xsector[nXSector].at2e_0 = nSprite;
+ continue;
+ }
+ }
+ break;
+ }
+ case 5:
+ {
+ int nOwner = sprite[nSprite].owner;
+ if (nOwner >= 0 && nOwner < numsectors)
+ {
+ int nXSector = sector[nOwner].extra;
+ if (nXSector > 0 && nXSector < kMaxXSectors)
+ {
+ xsector[nXSector].at2c_0 = nSprite;
+ continue;
+ }
+ }
+ break;
+ }
+ }
+ DeleteSprite(nSprite);
+ }
+}
+
+bool byte_1A76C6, byte_1A76C7, byte_1A76C8;
+
+MAPHEADER2 byte_19AE44;
+
+unsigned int dbReadMapCRC(const char *pPath)
+{
+ byte_1A76C7 = 0;
+ byte_1A76C8 = 0;
+ DICTNODE *pNode = gSysRes.Lookup(pPath, "MAP");
+ if (!pNode)
+ {
+ ThrowError("Error opening map file %s", pPath);
+ }
+ char *pData = (char*)gSysRes.Lock(pNode);
+ int nSize = pNode->size;
+ MAPSIGNATURE header;
+ IOBuffer(nSize, pData).Read(&header, 6);
+#if B_BIG_ENDIAN == 1
+ header.version = B_LITTLE16(header.version);
+#endif
+ if (memcmp(header.signature, "BLM\x1a", 4))
+ {
+ ThrowError("Map file corrupted");
+ }
+ if ((header.version & 0xff00) == 0x600)
+ {
+ }
+ else if ((header.version & 0xff00) == 0x700)
+ {
+ byte_1A76C8 = 1;
+ }
+ else
+ {
+ ThrowError("Map file is wrong version");
+ }
+ unsigned int nCRC = *(unsigned int*)(pData+nSize-4);
+ gSysRes.Unlock(pNode);
+ return nCRC;
+}
+
+int gMapRev, gSongId, gSkyCount;
+//char byte_19AE44[128];
+
+void dbLoadMap(const char *pPath, int *pX, int *pY, int *pZ, short *pAngle, short *pSector, unsigned int *pCRC)
+{
+ int16_t tpskyoff[256];
+ memset(show2dsector, 0, sizeof(show2dsector));
+ memset(show2dwall, 0, sizeof(show2dwall));
+ memset(show2dsprite, 0, sizeof(show2dsprite));
+#ifdef USE_OPENGL
+ Polymost_prepare_loadboard();
+#endif
+ {
+ char name2[BMAX_PATH];
+ Bstrncpy(name2, pPath, BMAX_PATH);
+ Bstrupr(name2);
+ DICTNODE* pNode = *gSysRes.Probe(name2, "MAP");
+ if (pNode && pNode->flags & DICT_EXTERNAL)
+ {
+ gSysRes.RemoveNode(pNode);
+ }
+ }
+ DICTNODE *pNode = gSysRes.Lookup(pPath, "MAP");
+ if (!pNode)
+ {
+ ThrowError("Error opening map file %s", pPath);
+ }
+ char *pData = (char*)gSysRes.Lock(pNode);
+ int nSize = pNode->size;
+ MAPSIGNATURE header;
+ IOBuffer IOBuffer1 = IOBuffer(nSize, pData);
+ IOBuffer1.Read(&header, 6);
+#if B_BIG_ENDIAN == 1
+ header.version = B_LITTLE16(header.version);
+#endif
+ if (memcmp(header.signature, "BLM\x1a", 4))
+ {
+ ThrowError("Map file corrupted");
+ }
+ byte_1A76C8 = 0;
+ if ((header.version & 0xff00) == 0x600)
+ {
+ }
+ else if ((header.version & 0xff00) == 0x700)
+ {
+ byte_1A76C8 = 1;
+ }
+ else
+ {
+ ThrowError("Map file is wrong version");
+ }
+ MAPHEADER mapHeader;
+ IOBuffer1.Read(&mapHeader,37/* sizeof(mapHeader)*/);
+ if (mapHeader.at16 != 0 && mapHeader.at16 != 0x7474614d && mapHeader.at16 != 0x4d617474)
+ {
+ dbCrypt((char*)&mapHeader, sizeof(mapHeader), 0x7474614d);
+ byte_1A76C7 = 1;
+ }
+#if B_BIG_ENDIAN == 1
+ mapHeader.at0 = B_LITTLE32(mapHeader.at0);
+ mapHeader.at4 = B_LITTLE32(mapHeader.at4);
+ mapHeader.at8 = B_LITTLE32(mapHeader.at8);
+ mapHeader.atc = B_LITTLE16(mapHeader.atc);
+ mapHeader.ate = B_LITTLE16(mapHeader.ate);
+ mapHeader.at10 = B_LITTLE16(mapHeader.at10);
+ mapHeader.at12 = B_LITTLE32(mapHeader.at12);
+ mapHeader.at16 = B_LITTLE32(mapHeader.at16);
+ mapHeader.at1b = B_LITTLE32(mapHeader.at1b);
+ mapHeader.at1f = B_LITTLE16(mapHeader.at1f);
+ mapHeader.at21 = B_LITTLE16(mapHeader.at21);
+ mapHeader.at23 = B_LITTLE16(mapHeader.at23);
+#endif
+
+ psky_t *pSky = tileSetupSky(0);
+ pSky->horizfrac = 65536;
+
+ *pX = mapHeader.at0;
+ *pY = mapHeader.at4;
+ *pZ = mapHeader.at8;
+ *pAngle = mapHeader.atc;
+ *pSector = mapHeader.ate;
+ pSky->lognumtiles = mapHeader.at10;
+ gVisibility = g_visibility = mapHeader.at12;
+ gSongId = mapHeader.at16;
+ if (byte_1A76C8)
+ {
+ if (mapHeader.at16 == 0x7474614d || mapHeader.at16 == 0x4d617474)
+ {
+ byte_1A76C6 = 1;
+ }
+ else if (!mapHeader.at16)
+ {
+ byte_1A76C6 = 0;
+ }
+ else
+ {
+ ThrowError("Corrupted Map file");
+ }
+ }
+ else if (mapHeader.at16)
+ {
+ ThrowError("Corrupted Map file");
+ }
+ parallaxtype = mapHeader.at1a;
+ gMapRev = mapHeader.at1b;
+ numsectors = mapHeader.at1f;
+ numwalls = mapHeader.at21;
+ dbInit();
+ if (byte_1A76C8)
+ {
+ IOBuffer1.Read(&byte_19AE44, 128);
+ dbCrypt((char*)&byte_19AE44, 128, numwalls);
+#if B_BIG_ENDIAN == 1
+ byte_19AE44.at40 = B_LITTLE32(byte_19AE44.at40);
+ byte_19AE44.at44 = B_LITTLE32(byte_19AE44.at44);
+ byte_19AE44.at48 = B_LITTLE32(byte_19AE44.at48);
+#endif
+ }
+ else
+ {
+ memset(&byte_19AE44, 0, 128);
+ }
+ gSkyCount = 1<lognumtiles;
+ IOBuffer1.Read(tpskyoff, gSkyCount*sizeof(tpskyoff[0]));
+ if (byte_1A76C8)
+ {
+ dbCrypt((char*)tpskyoff, gSkyCount*sizeof(tpskyoff[0]), gSkyCount*2);
+ }
+ for (int i = 0; i < ClipHigh(gSkyCount, MAXPSKYTILES); i++)
+ {
+ pSky->tileofs[i] = B_LITTLE16(tpskyoff[i]);
+ }
+ for (int i = 0; i < numsectors; i++)
+ {
+ sectortype *pSector = §or[i];
+ IOBuffer1.Read(pSector, sizeof(sectortype));
+ if (byte_1A76C8)
+ {
+ dbCrypt((char*)pSector, sizeof(sectortype), gMapRev*sizeof(sectortype));
+ }
+#if B_BIG_ENDIAN == 1
+ pSector->wallptr = B_LITTLE16(pSector->wallptr);
+ pSector->wallnum = B_LITTLE16(pSector->wallnum);
+ pSector->ceilingz = B_LITTLE32(pSector->ceilingz);
+ pSector->floorz = B_LITTLE32(pSector->floorz);
+ pSector->ceilingstat = B_LITTLE16(pSector->ceilingstat);
+ pSector->floorstat = B_LITTLE16(pSector->floorstat);
+ pSector->ceilingpicnum = B_LITTLE16(pSector->ceilingpicnum);
+ pSector->ceilingheinum = B_LITTLE16(pSector->ceilingheinum);
+ pSector->floorpicnum = B_LITTLE16(pSector->floorpicnum);
+ pSector->floorheinum = B_LITTLE16(pSector->floorheinum);
+ pSector->lotag = B_LITTLE16(pSector->lotag);
+ pSector->hitag = B_LITTLE16(pSector->hitag);
+ pSector->extra = B_LITTLE16(pSector->extra);
+#endif
+ qsector_filler[i] = pSector->fogpal;
+ pSector->fogpal = 0;
+ if (sector[i].extra > 0)
+ {
+ const int nXSectorSize = 60;
+ char pBuffer[nXSectorSize];
+ int nXSector = dbInsertXSector(i);
+ XSECTOR *pXSector = &xsector[nXSector];
+ memset(pXSector, 0, sizeof(XSECTOR));
+ int nCount;
+ if (!byte_1A76C8)
+ {
+ nCount = nXSectorSize;
+ }
+ else
+ {
+ nCount = byte_19AE44.at48;
+ }
+ dassert(nCount <= nXSectorSize);
+ IOBuffer1.Read(pBuffer, nCount);
+ BitReader bitReader(pBuffer, nCount);
+ pXSector->reference = bitReader.readSigned(14);
+ pXSector->state = bitReader.readUnsigned(1);
+ pXSector->busy = bitReader.readUnsigned(17);
+ pXSector->data = bitReader.readUnsigned(16);
+ pXSector->txID = bitReader.readUnsigned(10);
+ pXSector->at7_2 = bitReader.readUnsigned(3);
+ pXSector->at7_5 = bitReader.readUnsigned(3);
+ pXSector->rxID = bitReader.readUnsigned(10);
+ pXSector->command = bitReader.readUnsigned(8);
+ pXSector->triggerOn = bitReader.readUnsigned(1);
+ pXSector->triggerOff = bitReader.readUnsigned(1);
+ pXSector->busyTimeA = bitReader.readUnsigned(12);
+ pXSector->waitTimeA = bitReader.readUnsigned(12);
+ pXSector->atd_4 = bitReader.readUnsigned(1);
+ pXSector->interruptable = bitReader.readUnsigned(1);
+ pXSector->amplitude = bitReader.readSigned(8);
+ pXSector->freq = bitReader.readUnsigned(8);
+ pXSector->atf_6 = bitReader.readUnsigned(1);
+ pXSector->atf_7 = bitReader.readUnsigned(1);
+ pXSector->phase = bitReader.readUnsigned(8);
+ pXSector->wave = bitReader.readUnsigned(4);
+ pXSector->shadeAlways = bitReader.readUnsigned(1);
+ pXSector->shadeFloor = bitReader.readUnsigned(1);
+ pXSector->shadeCeiling = bitReader.readUnsigned(1);
+ pXSector->shadeWalls = bitReader.readUnsigned(1);
+ pXSector->shade = bitReader.readSigned(8);
+ pXSector->panAlways = bitReader.readUnsigned(1);
+ pXSector->panFloor = bitReader.readUnsigned(1);
+ pXSector->panCeiling = bitReader.readUnsigned(1);
+ pXSector->Drag = bitReader.readUnsigned(1);
+ pXSector->Underwater = bitReader.readUnsigned(1);
+ pXSector->Depth = bitReader.readUnsigned(3);
+ pXSector->panVel = bitReader.readUnsigned(8);
+ pXSector->panAngle = bitReader.readUnsigned(11);
+ pXSector->at16_3 = bitReader.readUnsigned(1);
+ pXSector->decoupled = bitReader.readUnsigned(1);
+ pXSector->triggerOnce = bitReader.readUnsigned(1);
+ pXSector->at16_6 = bitReader.readUnsigned(1);
+ pXSector->Key = bitReader.readUnsigned(3);
+ pXSector->Push = bitReader.readUnsigned(1);
+ pXSector->Vector = bitReader.readUnsigned(1);
+ pXSector->Reserved = bitReader.readUnsigned(1);
+ pXSector->Enter = bitReader.readUnsigned(1);
+ pXSector->Exit = bitReader.readUnsigned(1);
+ pXSector->Wallpush = bitReader.readUnsigned(1);
+ pXSector->color = bitReader.readUnsigned(1);
+ pXSector->at18_1 = bitReader.readUnsigned(1);
+ pXSector->busyTimeB = bitReader.readUnsigned(12);
+ pXSector->waitTimeB = bitReader.readUnsigned(12);
+ pXSector->at1b_2 = bitReader.readUnsigned(1);
+ pXSector->at1b_3 = bitReader.readUnsigned(1);
+ pXSector->ceilpal = bitReader.readUnsigned(4);
+ pXSector->at1c_0 = bitReader.readSigned(32);
+ pXSector->at20_0 = bitReader.readSigned(32);
+ pXSector->at24_0 = bitReader.readSigned(32);
+ pXSector->at28_0 = bitReader.readSigned(32);
+ pXSector->at2c_0 = bitReader.readUnsigned(16);
+ pXSector->at2e_0 = bitReader.readUnsigned(16);
+ pXSector->Crush = bitReader.readUnsigned(1);
+ pXSector->at30_1 = bitReader.readUnsigned(8);
+ pXSector->at31_1 = bitReader.readUnsigned(8);
+ pXSector->at32_1 = bitReader.readUnsigned(8);
+ pXSector->damageType = bitReader.readUnsigned(3);
+ pXSector->floorpal = bitReader.readUnsigned(4);
+ pXSector->at34_0 = bitReader.readUnsigned(8);
+ pXSector->locked = bitReader.readUnsigned(1);
+ pXSector->windVel = bitReader.readUnsigned(10);
+ pXSector->windAng = bitReader.readUnsigned(11);
+ pXSector->windAlways = bitReader.readUnsigned(1);
+ pXSector->at37_7 = bitReader.readUnsigned(1);
+ pXSector->bobTheta = bitReader.readUnsigned(11);
+ pXSector->bobZRange = bitReader.readUnsigned(5);
+ pXSector->bobSpeed = bitReader.readSigned(12);
+ pXSector->bobAlways = bitReader.readUnsigned(1);
+ pXSector->bobFloor = bitReader.readUnsigned(1);
+ pXSector->bobCeiling = bitReader.readUnsigned(1);
+ pXSector->bobRotate = bitReader.readUnsigned(1);
+ xsector[sector[i].extra].reference = i;
+ xsector[sector[i].extra].busy = xsector[sector[i].extra].state<<16;
+ }
+ }
+ for (int i = 0; i < numwalls; i++)
+ {
+ walltype *pWall = &wall[i];
+ IOBuffer1.Read(pWall, sizeof(walltype));
+ if (byte_1A76C8)
+ {
+ dbCrypt((char*)pWall, sizeof(walltype), (gMapRev*sizeof(sectortype)) | 0x7474614d);
+ }
+#if B_BIG_ENDIAN == 1
+ pWall->x = B_LITTLE32(pWall->x);
+ pWall->y = B_LITTLE32(pWall->y);
+ pWall->point2 = B_LITTLE16(pWall->point2);
+ pWall->nextwall = B_LITTLE16(pWall->nextwall);
+ pWall->nextsector = B_LITTLE16(pWall->nextsector);
+ pWall->cstat = B_LITTLE16(pWall->cstat);
+ pWall->picnum = B_LITTLE16(pWall->picnum);
+ pWall->overpicnum = B_LITTLE16(pWall->overpicnum);
+ pWall->lotag = B_LITTLE16(pWall->lotag);
+ pWall->hitag = B_LITTLE16(pWall->hitag);
+ pWall->extra = B_LITTLE16(pWall->extra);
+#endif
+ if (wall[i].extra > 0)
+ {
+ const int nXWallSize = 24;
+ char pBuffer[nXWallSize];
+ int nXWall = dbInsertXWall(i);
+ XWALL *pXWall = &xwall[nXWall];
+ memset(pXWall, 0, sizeof(XWALL));
+ int nCount;
+ if (!byte_1A76C8)
+ {
+ nCount = nXWallSize;
+ }
+ else
+ {
+ nCount = byte_19AE44.at44;
+ }
+ dassert(nCount <= nXWallSize);
+ IOBuffer1.Read(pBuffer, nCount);
+ BitReader bitReader(pBuffer, nCount);
+ pXWall->reference = bitReader.readSigned(14);
+ pXWall->state = bitReader.readUnsigned(1);
+ pXWall->busy = bitReader.readUnsigned(17);
+ pXWall->data = bitReader.readSigned(16);
+ pXWall->txID = bitReader.readUnsigned(10);
+ pXWall->at7_2 = bitReader.readUnsigned(6);
+ pXWall->rxID = bitReader.readUnsigned(10);
+ pXWall->command = bitReader.readUnsigned(8);
+ pXWall->triggerOn = bitReader.readUnsigned(1);
+ pXWall->triggerOff = bitReader.readUnsigned(1);
+ pXWall->busyTime = bitReader.readUnsigned(12);
+ pXWall->waitTime = bitReader.readUnsigned(12);
+ pXWall->restState = bitReader.readUnsigned(1);
+ pXWall->interruptable = bitReader.readUnsigned(1);
+ pXWall->panAlways = bitReader.readUnsigned(1);
+ pXWall->panXVel = bitReader.readSigned(8);
+ pXWall->panYVel = bitReader.readSigned(8);
+ pXWall->decoupled = bitReader.readUnsigned(1);
+ pXWall->triggerOnce = bitReader.readUnsigned(1);
+ pXWall->isTriggered = bitReader.readUnsigned(1);
+ pXWall->key = bitReader.readUnsigned(3);
+ pXWall->triggerPush = bitReader.readUnsigned(1);
+ pXWall->triggerVector = bitReader.readUnsigned(1);
+ pXWall->triggerReserved = bitReader.readUnsigned(1);
+ pXWall->at11_0 = bitReader.readUnsigned(2);
+ pXWall->xpanFrac = bitReader.readUnsigned(8);
+ pXWall->ypanFrac = bitReader.readUnsigned(8);
+ pXWall->locked = bitReader.readUnsigned(1);
+ pXWall->dudeLockout = bitReader.readUnsigned(1);
+ pXWall->at13_4 = bitReader.readUnsigned(4);
+ pXWall->at14_0 = bitReader.readUnsigned(32);
+ xwall[wall[i].extra].reference = i;
+ xwall[wall[i].extra].busy = xwall[wall[i].extra].state << 16;
+ }
+ }
+ initspritelists();
+ for (int i = 0; i < mapHeader.at23; i++)
+ {
+ RemoveSpriteStat(i);
+ spritetype *pSprite = &sprite[i];
+ IOBuffer1.Read(pSprite, sizeof(spritetype));
+ if (byte_1A76C8)
+ {
+ dbCrypt((char*)pSprite, sizeof(spritetype), (gMapRev*sizeof(spritetype)) | 0x7474614d);
+ }
+#if B_BIG_ENDIAN == 1
+ pSprite->x = B_LITTLE32(pSprite->x);
+ pSprite->y = B_LITTLE32(pSprite->y);
+ pSprite->z = B_LITTLE32(pSprite->z);
+ pSprite->cstat = B_LITTLE16(pSprite->cstat);
+ pSprite->picnum = B_LITTLE16(pSprite->picnum);
+ pSprite->sectnum = B_LITTLE16(pSprite->sectnum);
+ pSprite->statnum = B_LITTLE16(pSprite->statnum);
+ pSprite->ang = B_LITTLE16(pSprite->ang);
+ pSprite->owner = B_LITTLE16(pSprite->owner);
+ pSprite->xvel = B_LITTLE16(pSprite->xvel);
+ pSprite->yvel = B_LITTLE16(pSprite->yvel);
+ pSprite->zvel = B_LITTLE16(pSprite->zvel);
+ pSprite->lotag = B_LITTLE16(pSprite->lotag);
+ pSprite->hitag = B_LITTLE16(pSprite->hitag);
+ pSprite->extra = B_LITTLE16(pSprite->extra);
+#endif
+ // NoOne's extension bit
+ if (pSprite->hitag&1)
+ {
+ pSprite->hitag &= ~1;
+ pSprite->hitag |= kHitagExtBit;
+ }
+ InsertSpriteSect(i, sprite[i].sectnum);
+ InsertSpriteStat(i, sprite[i].statnum);
+ sprite[i].index = i;
+ qsprite_filler[i] = pSprite->blend;
+ pSprite->blend = 0;
+ if (sprite[i].extra > 0)
+ {
+ const int nXSpriteSize = 56;
+ char pBuffer[nXSpriteSize];
+ int nXSprite = dbInsertXSprite(i);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ memset(pXSprite, 0, sizeof(XSPRITE));
+ int nCount;
+ if (!byte_1A76C8)
+ {
+ nCount = nXSpriteSize;
+ }
+ else
+ {
+ nCount = byte_19AE44.at40;
+ }
+ dassert(nCount <= nXSpriteSize);
+ IOBuffer1.Read(pBuffer, nCount);
+ BitReader bitReader(pBuffer, nCount);
+ pXSprite->reference = bitReader.readSigned(14);
+ pXSprite->state = bitReader.readUnsigned(1);
+ pXSprite->busy = bitReader.readUnsigned(17);
+ pXSprite->txID = bitReader.readUnsigned(10);
+ pXSprite->rxID = bitReader.readUnsigned(10);
+ pXSprite->command = bitReader.readUnsigned(8);
+ pXSprite->triggerOn = bitReader.readUnsigned(1);
+ pXSprite->triggerOff = bitReader.readUnsigned(1);
+ pXSprite->wave = bitReader.readUnsigned(2);
+ pXSprite->busyTime = bitReader.readUnsigned(12);
+ pXSprite->waitTime = bitReader.readUnsigned(12);
+ pXSprite->restState = bitReader.readUnsigned(1);
+ pXSprite->Interrutable = bitReader.readUnsigned(1);
+ pXSprite->atb_2 = bitReader.readUnsigned(2);
+ pXSprite->respawnPending = bitReader.readUnsigned(2);
+ pXSprite->atb_6 = bitReader.readUnsigned(1);
+ pXSprite->lT = bitReader.readUnsigned(1);
+ pXSprite->dropMsg = bitReader.readUnsigned(8);
+ pXSprite->Decoupled = bitReader.readUnsigned(1);
+ pXSprite->triggerOnce = bitReader.readUnsigned(1);
+ pXSprite->isTriggered = bitReader.readUnsigned(1);
+ pXSprite->key = bitReader.readUnsigned(3);
+ pXSprite->Push = bitReader.readUnsigned(1);
+ pXSprite->Vector = bitReader.readUnsigned(1);
+ pXSprite->Impact = bitReader.readUnsigned(1);
+ pXSprite->Pickup = bitReader.readUnsigned(1);
+ pXSprite->Touch = bitReader.readUnsigned(1);
+ pXSprite->Sight = bitReader.readUnsigned(1);
+ pXSprite->Proximity = bitReader.readUnsigned(1);
+ pXSprite->ate_5 = bitReader.readUnsigned(2);
+ pXSprite->lSkill = bitReader.readUnsigned(5);
+ pXSprite->lS = bitReader.readUnsigned(1);
+ pXSprite->lB = bitReader.readUnsigned(1);
+ pXSprite->lC = bitReader.readUnsigned(1);
+ pXSprite->DudeLockout = bitReader.readUnsigned(1);
+ pXSprite->data1 = bitReader.readSigned(16);
+ pXSprite->data2 = bitReader.readSigned(16);
+ pXSprite->data3 = bitReader.readSigned(16);
+ pXSprite->goalAng = bitReader.readUnsigned(11);
+ pXSprite->dodgeDir = bitReader.readSigned(2);
+ pXSprite->locked = bitReader.readUnsigned(1);
+ pXSprite->medium = bitReader.readUnsigned(2);
+ pXSprite->respawn = bitReader.readUnsigned(2);
+ pXSprite->data4 = bitReader.readUnsigned(16);
+ pXSprite->at1a_2 = bitReader.readUnsigned(6);
+ pXSprite->lockMsg = bitReader.readUnsigned(8);
+ pXSprite->health = bitReader.readUnsigned(12);
+ pXSprite->dudeDeaf = bitReader.readUnsigned(1);
+ pXSprite->dudeAmbush = bitReader.readUnsigned(1);
+ pXSprite->dudeGuard = bitReader.readUnsigned(1);
+ pXSprite->dudeFlag4 = bitReader.readUnsigned(1);
+ pXSprite->target = bitReader.readSigned(16);
+ pXSprite->targetX = bitReader.readSigned(32);
+ pXSprite->targetY = bitReader.readSigned(32);
+ pXSprite->targetZ = bitReader.readSigned(32);
+ pXSprite->burnTime = bitReader.readUnsigned(16);
+ pXSprite->burnSource = bitReader.readSigned(16);
+ pXSprite->height = bitReader.readUnsigned(16);
+ pXSprite->stateTimer = bitReader.readUnsigned(16);
+ pXSprite->aiState = NULL;
+ bitReader.skipBits(32);
+ xsprite[sprite[i].extra].reference = i;
+ xsprite[sprite[i].extra].busy = xsprite[sprite[i].extra].state << 16;
+ if (!byte_1A76C8)
+ {
+ xsprite[sprite[i].extra].lT |= xsprite[sprite[i].extra].lB;
+ }
+ }
+ if ((sprite[i].cstat & 0x30) == 0x30)
+ {
+ sprite[i].cstat &= ~0x30;
+ }
+ }
+ unsigned int nCRC;
+ IOBuffer1.Read(&nCRC, 4);
+#if B_BIG_ENDIAN == 1
+ nCRC = B_LITTLE32(nCRC);
+#endif
+ if (Bcrc32(pData, nSize-4, 0) != nCRC)
+ {
+ ThrowError("Map File does not match CRC");
+ }
+ *pCRC = nCRC;
+ gSysRes.Unlock(pNode);
+ PropagateMarkerReferences();
+ if (byte_1A76C8)
+ {
+ if (gSongId == 0x7474614d || gSongId == 0x4d617474)
+ {
+ byte_1A76C6 = 1;
+ }
+ else if (!gSongId)
+ {
+ byte_1A76C6 = 0;
+ }
+ else
+ {
+ ThrowError("Corrupted Map file");
+ }
+ }
+ else if (gSongId != 0)
+ {
+ ThrowError("Corrupted Shareware Map file");
+ }
+
+#ifdef POLYMER
+ if (videoGetRenderMode() == REND_POLYMER)
+ polymer_loadboard();
+#endif
+
+ if ((header.version & 0xff00) == 0x600)
+ {
+ switch (header.version&0xff)
+ {
+ case 0:
+ for (int i = 0; i < numsectors; i++)
+ {
+ sectortype *pSector = §or[i];
+ if (pSector->extra > 0)
+ {
+ XSECTOR *pXSector = &xsector[pSector->extra];
+ pXSector->busyTimeB = pXSector->busyTimeA;
+ if (pXSector->busyTimeA > 0)
+ {
+ if (!pXSector->atd_4)
+ {
+ pXSector->atf_6 = 1;
+ }
+ else
+ {
+ pXSector->waitTimeB = pXSector->busyTimeA;
+ pXSector->waitTimeA = 0;
+ pXSector->atf_7 = 1;
+ }
+ }
+ }
+ }
+ fallthrough__;
+ case 1:
+ for (int i = 0; i < numsectors; i++)
+ {
+ sectortype *pSector = §or[i];
+ if (pSector->extra > 0)
+ {
+ XSECTOR *pXSector = &xsector[pSector->extra];
+ pXSector->freq >>= 1;
+ }
+ }
+ fallthrough__;
+ case 2:
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ }
+ break;
+
+ }
+ }
+}
diff --git a/source/blood/src/db.h b/source/blood/src/db.h
new file mode 100644
index 000000000..b08c51ed0
--- /dev/null
+++ b/source/blood/src/db.h
@@ -0,0 +1,299 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#define kMaxXSprites 2048
+#define kMaxXWalls 512
+#define kMaxXSectors 512
+
+#pragma pack(push, 1)
+
+struct AISTATE;
+
+struct XSPRITE {
+ //int at0;
+ unsigned int atb_2 : 2; // unused //
+ unsigned int atb_6 : 1; // unused // let's use these to add more data
+ unsigned int ate_5 : 2; // unused // fields in the future? must be signed also
+ unsigned int at1a_2 : 6; // unused //
+
+ signed int reference : 14; // at0_0
+ unsigned int state : 1; // State 0
+ unsigned int busy : 17;
+ unsigned int txID : 10; // TX ID
+ unsigned int rxID : 10; // RX ID
+ unsigned int command : 8; // Cmd
+ unsigned int triggerOn : 1; // going ON
+ unsigned int triggerOff : 1; // going OFF
+ unsigned int busyTime : 12; // busyTime
+ unsigned int waitTime : 12; // waitTime
+ unsigned int restState : 1; // restState
+ unsigned int Interrutable : 1; // Interruptable
+
+ unsigned int respawnPending : 2; // respawnPending
+
+ signed int dropMsg : 10; // Drop Item
+ unsigned int Decoupled : 1; // Decoupled
+ unsigned int triggerOnce : 1; // 1-shot
+ unsigned int isTriggered : 1; // works in case if triggerOnce selected
+
+ unsigned int key : 3; // Key
+ unsigned int wave : 2; // Wave
+ unsigned int Push: 1; // Push
+ unsigned int Vector : 1; // Vector
+ unsigned int Impact : 1; // Impact
+ unsigned int Pickup : 1; // Pickup
+ unsigned int Touch : 1; // Touch
+ unsigned int Sight : 1; // Sight
+ unsigned int Proximity : 1; // Proximity
+ unsigned int lSkill : 5; // Launch 12345
+ unsigned int lS : 1; // Single
+ unsigned int lB : 1; // Bloodbath
+ unsigned int lT : 1; // Launch Team
+ unsigned int lC : 1; // Coop
+ unsigned int DudeLockout : 1; // DudeLockout
+ signed int data1 : 16; // Data 1
+ signed int data2 : 16; // Data 2
+ signed int data3 : 16; // Data 3
+ unsigned int data4 : 16; // Data 4
+ unsigned int locked : 1; // Locked
+ unsigned int medium : 2; // medium
+ unsigned int respawn : 2; // Respawn option
+ unsigned int lockMsg : 8; // Lock msg
+ unsigned int health : 20; // 1c_0
+ unsigned int dudeDeaf : 1; // dudeDeaf
+ unsigned int dudeAmbush : 1; // dudeAmbush
+ unsigned int dudeGuard : 1; // dudeGuard
+ unsigned int dudeFlag4 : 1; // DF reserved
+ signed int target : 16; // target sprite
+ signed int targetX : 32; // target x
+ signed int targetY : 32; // target y
+ signed int targetZ : 32; // target z
+ unsigned int goalAng : 11; // Dude goal ang
+ signed int dodgeDir : 2; // Dude dodge direction
+ unsigned int burnTime : 16;
+ signed int burnSource : 16;
+ unsigned int height : 16;
+ unsigned int stateTimer : 16; // ai timer
+ AISTATE *aiState; // ai
+ signed int txIndex : 10; // used by kGDXSequentialTX to keep current TX ID index
+ signed int cumulDamage : 16; // for dudes
+ signed int scale; // used for scaling SEQ size on sprites
+};
+
+struct XSECTOR {
+ signed int reference : 14;
+ unsigned int state : 1; // State 0
+ unsigned int busy : 17;
+ unsigned int data : 16; // Data
+ unsigned int txID : 10; // TX ID
+ unsigned int rxID : 10; // RX ID
+ unsigned int at7_2 : 3; // OFF->ON wave
+ unsigned int at7_5 : 3; // ON->OFF wave
+
+ unsigned int command : 8; // Cmd 0
+ unsigned int triggerOn : 1; // Send at ON
+ unsigned int triggerOff : 1; // Send at OFF
+ unsigned int busyTimeA : 12; // OFF->ON busyTime
+ unsigned int waitTimeA : 12; // OFF->ON waitTime
+ unsigned int atd_4 : 1;
+ unsigned int interruptable : 1; // Interruptable
+
+ unsigned int atf_6 : 1; // OFF->ON wait
+ unsigned int atf_7 : 1; // ON->OFF wait
+ signed int amplitude : 8; // Lighting amplitude
+ unsigned int freq : 8; // Lighting freq
+ unsigned int phase : 8; // Lighting phase
+ unsigned int wave : 4; // Lighting wave
+ unsigned int shadeAlways : 1; // Lighting shadeAlways
+ unsigned int shadeFloor : 1; // Lighting floor
+ unsigned int shadeCeiling : 1; // Lighting ceiling
+ unsigned int shadeWalls : 1; // Lighting walls
+ signed int shade : 8; // Lighting value
+ unsigned int panAlways : 1; // Pan always
+ unsigned int panFloor : 1; // Pan floor
+ unsigned int panCeiling : 1; // Pan ceiling
+ unsigned int Drag : 1; // Pan drag
+ unsigned int panVel : 8; // Motion speed
+ unsigned int panAngle : 11; // Motion angle
+ unsigned int Underwater : 1; // Underwater
+ unsigned int Depth : 3; // Depth
+ unsigned int at16_3 : 1;
+ unsigned int decoupled : 1; // Decoupled
+ unsigned int triggerOnce : 1; // 1-shot
+ unsigned int at16_6 : 1;
+ unsigned int Key : 3; // Key
+ unsigned int Push : 1; // Push
+ unsigned int Vector : 1; // Vector
+ unsigned int Reserved : 1; // Reserved
+ unsigned int Enter : 1; // Enter
+ unsigned int Exit : 1; // Exit
+ unsigned int Wallpush : 1; // WallPush
+ unsigned int color : 1; // Color Lights
+ unsigned int at18_1 : 1;
+ unsigned int busyTimeB : 12; // ON->OFF busyTime
+ unsigned int waitTimeB : 12; // ON->OFF waitTime
+ unsigned int at1b_2 : 1;
+ unsigned int at1b_3 : 1;
+ unsigned int ceilpal : 4; // Ceil pal2
+ signed int at1c_0 : 32;
+ signed int at20_0 : 32;
+ signed int at24_0 : 32;
+ signed int at28_0 : 32;
+ unsigned int at2c_0 : 16;
+ unsigned int at2e_0 : 16;
+ unsigned int Crush : 1; // Crush
+ unsigned int at30_1 : 8; // Ceiling x panning frac
+ unsigned int at31_1 : 8; // Ceiling y panning frac
+ unsigned int at32_1 : 8; // Floor x panning frac
+ unsigned int damageType : 3; // DamageType
+ unsigned int floorpal : 4; // Floor pal2
+ unsigned int at34_0 : 8; // Floor y panning frac
+ unsigned int locked : 1; // Locked
+ unsigned int windVel; // Wind vel (by NoOne: changed from 10 bit to use higher velocity values)
+ unsigned int windAng : 11; // Wind ang
+ unsigned int windAlways : 1; // Wind always
+ unsigned int at37_7 : 1;
+ unsigned int bobTheta : 11; // Motion Theta
+ unsigned int bobZRange : 5; // Motion Z range
+ signed int bobSpeed : 12; // Motion speed
+ unsigned int bobAlways : 1; // Motion always
+ unsigned int bobFloor : 1; // Motion bob floor
+ unsigned int bobCeiling : 1; // Motion bob ceiling
+ unsigned int bobRotate : 1; // Motion rotate
+}; // 60(0x3c) bytes
+
+struct XWALL {
+ signed int reference : 14;
+ unsigned int state : 1; // State
+ unsigned int busy : 17;
+ signed int data : 16; // Data
+ unsigned int txID : 10; // TX ID
+ unsigned int at7_2 : 6; // unused
+ unsigned int rxID : 10; // RX ID
+ unsigned int command : 8; // Cmd
+ unsigned int triggerOn : 1; // going ON
+ unsigned int triggerOff : 1; // going OFF
+ unsigned int busyTime : 12; // busyTime
+ unsigned int waitTime : 12; // waitTime
+ unsigned int restState : 1; // restState
+ unsigned int interruptable : 1; // Interruptable
+ unsigned int panAlways : 1; // panAlways
+ signed int panXVel : 8; // panX
+ signed int panYVel : 8; // panY
+ unsigned int decoupled : 1; // Decoupled
+ unsigned int triggerOnce : 1; // 1-shot
+ unsigned int isTriggered : 1;
+ unsigned int key : 3; // Key
+ unsigned int triggerPush : 1; // Push
+ unsigned int triggerVector : 1; // Vector
+ unsigned int triggerReserved : 1; // Reserved
+ unsigned int at11_0 : 2; // unused
+ unsigned int xpanFrac : 8; // x panning frac
+ unsigned int ypanFrac : 8; // y panning frac
+ unsigned int locked : 1; // Locked
+ unsigned int dudeLockout : 1; // DudeLockout
+ unsigned int at13_4 : 4; // unused;
+ unsigned int at14_0 : 32; // unused
+}; // 24(0x18) bytes
+
+struct MAPSIGNATURE {
+ char signature[4];
+ short version;
+};
+
+struct MAPHEADER {
+ int at0; // x
+ int at4; // y
+ int at8; // z
+ short atc; // ang
+ short ate; // sect
+ short at10; // pskybits
+ int at12; // visibility
+ int at16; // song id, Matt
+ char at1a; // parallaxtype
+ int at1b; // map revision
+ short at1f; // numsectors
+ short at21; // numwalls
+ short at23; // numsprites
+};
+
+struct MAPHEADER2 {
+ char at0[64];
+ int at40; // xsprite size
+ int at44; // xwall size
+ int at48; // xsector size
+ char pad[52];
+};
+
+#pragma pack(pop)
+
+extern unsigned short gStatCount[kMaxStatus + 1];;
+
+extern bool byte_1A76C6, byte_1A76C7, byte_1A76C8;
+extern MAPHEADER2 byte_19AE44;
+
+extern XSPRITE xsprite[kMaxXSprites];
+extern XSECTOR xsector[kMaxXSectors];
+extern XWALL xwall[kMaxXWalls];
+
+extern int xvel[kMaxSprites], yvel[kMaxSprites], zvel[kMaxSprites];
+
+extern int gVisibility;
+extern int gMapRev, gSongId, gSkyCount;
+extern const char *gItemText[];
+extern const char *gAmmoText[];
+extern const char *gWeaponText[];
+
+extern unsigned short nextXSprite[kMaxXSprites];
+extern unsigned short nextXWall[kMaxXWalls];
+extern unsigned short nextXSector[kMaxXSectors];
+
+void InsertSpriteSect(int nSprite, int nSector);
+void RemoveSpriteSect(int nSprite);
+void InsertSpriteStat(int nSprite, int nStat);
+void RemoveSpriteStat(int nSprite);
+void qinitspritelists(void);
+int InsertSprite(int nSector, int nStat);
+int qinsertsprite(short nSector, short nStat);
+int DeleteSprite(int nSprite);
+int qdeletesprite(short nSprite);
+int ChangeSpriteSect(int nSprite, int nSector);
+int qchangespritesect(short nSprite, short nSector);
+int ChangeSpriteStat(int nSprite, int nStatus);
+int qchangespritestat(short nSprite, short nStatus);
+void InitFreeList(unsigned short *pList, int nCount);
+void InsertFree(unsigned short *pList, int nIndex);
+unsigned short dbInsertXSprite(int nSprite);
+void dbDeleteXSprite(int nXSprite);
+unsigned short dbInsertXWall(int nWall);
+void dbDeleteXWall(int nXWall);
+unsigned short dbInsertXSector(int nSector);
+void dbDeleteXSector(int nXSector);
+void dbXSpriteClean(void);
+void dbXWallClean(void);
+void dbXSectorClean(void);
+void dbInit(void);
+void PropagateMarkerReferences(void);
+unsigned int dbReadMapCRC(const char *pPath);
+void dbLoadMap(const char *pPath, int *pX, int *pY, int *pZ, short *pAngle, short *pSector, unsigned int *pCRC);
diff --git a/source/blood/src/demo.cpp b/source/blood/src/demo.cpp
new file mode 100644
index 000000000..44d0525e4
--- /dev/null
+++ b/source/blood/src/demo.cpp
@@ -0,0 +1,625 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include "common.h"
+#include "common_game.h"
+#include "keyboard.h"
+#include "control.h"
+#include "osd.h"
+#include "mmulti.h"
+
+#include "blood.h"
+#include "controls.h"
+#include "demo.h"
+#include "fire.h"
+#include "gamemenu.h"
+#include "globals.h"
+#include "levels.h"
+#include "menu.h"
+#include "messages.h"
+#include "misc.h"
+#include "music.h"
+#include "network.h"
+#include "player.h"
+#include "screen.h"
+#include "view.h"
+
+int nBuild = 0;
+
+void ReadGameOptionsLegacy(GAMEOPTIONS &gameOptions, GAMEOPTIONSLEGACY &gameOptionsLegacy)
+{
+ gameOptions.nGameType = gameOptionsLegacy.nGameType;
+ gameOptions.nDifficulty = gameOptionsLegacy.nDifficulty;
+ gameOptions.nEpisode = gameOptionsLegacy.nEpisode;
+ gameOptions.nLevel = gameOptionsLegacy.nLevel;
+ strcpy(gameOptions.zLevelName, gameOptionsLegacy.zLevelName);
+ strcpy(gameOptions.zLevelSong, gameOptionsLegacy.zLevelSong);
+ gameOptions.nTrackNumber = gameOptionsLegacy.nTrackNumber;
+ strcpy(gameOptions.szSaveGameName, gameOptionsLegacy.szSaveGameName);
+ strcpy(gameOptions.szUserGameName, gameOptionsLegacy.szUserGameName);
+ gameOptions.nSaveGameSlot = gameOptionsLegacy.nSaveGameSlot;
+ gameOptions.picEntry = gameOptionsLegacy.picEntry;
+ gameOptions.uMapCRC = gameOptionsLegacy.uMapCRC;
+ gameOptions.nMonsterSettings = gameOptionsLegacy.nMonsterSettings;
+ gameOptions.uGameFlags = gameOptionsLegacy.uGameFlags;
+ gameOptions.uNetGameFlags = gameOptionsLegacy.uNetGameFlags;
+ gameOptions.nWeaponSettings = gameOptionsLegacy.nWeaponSettings;
+ gameOptions.nItemSettings = gameOptionsLegacy.nItemSettings;
+ gameOptions.nRespawnSettings = gameOptionsLegacy.nRespawnSettings;
+ gameOptions.nTeamSettings = gameOptionsLegacy.nTeamSettings;
+ gameOptions.nMonsterRespawnTime = gameOptionsLegacy.nMonsterRespawnTime;
+ gameOptions.nWeaponRespawnTime = gameOptionsLegacy.nWeaponRespawnTime;
+ gameOptions.nItemRespawnTime = gameOptionsLegacy.nItemRespawnTime;
+ gameOptions.nSpecialRespawnTime = gameOptionsLegacy.nSpecialRespawnTime;
+}
+
+CDemo gDemo;
+
+CDemo::CDemo()
+{
+ nBuild = 4;
+ at0 = 0;
+ at1 = 0;
+ at3 = 0;
+ hPFile = -1;
+ hRFile = NULL;
+ atb = 0;
+ pFirstDemo = NULL;
+ pCurrentDemo = NULL;
+ at59ef = 0;
+ at2 = 0;
+ memset(&atf, 0, sizeof(atf));
+ m_bLegacy = false;
+}
+
+CDemo::~CDemo()
+{
+ at0 = 0;
+ at1 = 0;
+ at3 = 0;
+ atb = 0;
+ memset(&atf, 0, sizeof(atf));
+ if (hPFile >= 0)
+ {
+ kclose(hPFile);
+ hPFile = -1;
+ }
+ if (hRFile != NULL)
+ {
+ fclose(hRFile);
+ hRFile = NULL;
+ }
+ auto pNextDemo = pFirstDemo;
+ for (auto pDemo = pFirstDemo; pDemo != NULL; pDemo = pNextDemo)
+ {
+ pNextDemo = pDemo->pNext;
+ delete pDemo;
+ }
+ pFirstDemo = NULL;
+ pCurrentDemo = NULL;
+ at59ef = 0;
+ m_bLegacy = false;
+}
+
+bool CDemo::Create(const char *pzFile)
+{
+ char buffer[BMAX_PATH];
+ char vc = 0;
+ if (at0 || at1)
+ ThrowError("CDemo::Create called during demo record/playback process.");
+ if (!pzFile)
+ {
+ for (int i = 0; i < 8 && !vc; i++)
+ {
+ G_ModDirSnprintf(buffer, BMAX_PATH, "%s0%02d.dem", BloodIniPre, i);
+ if (access(buffer, F_OK) != -1)
+ vc = 1;
+ }
+ if (vc == 1)
+ {
+ hRFile = fopen(buffer, "wb");
+ if (hRFile == NULL)
+ return false;
+ }
+ }
+ else
+ {
+ G_ModDirSnprintfLite(buffer, BMAX_PATH, pzFile);
+ hRFile = fopen(buffer, "wb");
+ if (hRFile == NULL)
+ return false;
+ }
+ at0 = 1;
+ atb = 0;
+ return true;
+}
+
+void CDemo::Write(GINPUT *pPlayerInputs)
+{
+ dassert(pPlayerInputs != NULL);
+ if (!at0)
+ return;
+ if (atb == 0)
+ {
+ atf.signature = 0x1a4d4445; // '\x1aMDE';
+ atf.nVersion = BYTEVERSION;
+ atf.nBuild = nBuild;
+ atf.nInputCount = 0;
+ atf.nNetPlayers = gNetPlayers;
+ atf.nMyConnectIndex = myconnectindex;
+ atf.nConnectHead = connecthead;
+ memcpy(atf.connectPoints, connectpoint2, sizeof(atf.connectPoints));
+ memcpy(&m_gameOptions, &gGameOptions, sizeof(gGameOptions));
+ fwrite(&atf, sizeof(DEMOHEADER), 1, hRFile);
+ fwrite(&m_gameOptions, sizeof(GAMEOPTIONS), 1, hRFile);
+ }
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ memcpy(&at1aa[atb&1023], &pPlayerInputs[p], sizeof(GINPUT));
+ atb++;
+ if((atb&(kInputBufferSize-1))==0)
+ FlushInput(kInputBufferSize);
+ }
+}
+
+void CDemo::Close(void)
+{
+ if (at0)
+ {
+ if (atb&(kInputBufferSize-1))
+ FlushInput(atb&(kInputBufferSize-1));
+ atf.nInputCount = atb;
+ fseek(hRFile, 0, SEEK_SET);
+ fwrite(&atf, sizeof(DEMOHEADER), 1, hRFile);
+ fwrite(&m_gameOptions, sizeof(GAMEOPTIONS), 1, hRFile);
+ }
+ if (hPFile >= 0)
+ {
+ kclose(hPFile);
+ hPFile = -1;
+ }
+ if (hRFile != NULL)
+ {
+ fclose(hRFile);
+ hRFile = NULL;
+ }
+ at0 = 0;
+ at1 = 0;
+}
+
+bool CDemo::SetupPlayback(const char *pzFile)
+{
+ at0 = 0;
+ at1 = 0;
+ if (pzFile)
+ {
+ hPFile = kopen4loadfrommod(pzFile, 0);
+ if (hPFile == -1)
+ return false;
+ }
+ else
+ {
+ if (!pCurrentDemo)
+ return false;
+ hPFile = kopen4loadfrommod(pCurrentDemo->zName, 0);
+ if (hPFile == -1)
+ return false;
+ }
+ kread(hPFile, &atf, sizeof(DEMOHEADER));
+#if B_BIG_ENDIAN == 1
+ atf.signature = B_LITTLE32(atf.signature);
+ atf.nVersion = B_LITTLE16(atf.nVersion);
+ atf.nBuild = B_LITTLE32(atf.nBuild);
+ atf.nInputCount = B_LITTLE32(atf.nInputCount);
+ atf.nNetPlayers = B_LITTLE32(atf.nNetPlayers);
+ atf.nMyConnectIndex = B_LITTLE16(atf.nMyConnectIndex);
+ atf.nConnectHead = B_LITTLE16(atf.nConnectHead);
+ atf.nMyConnectIndex = B_LITTLE16(atf.nMyConnectIndex);
+ for (int i = 0; i < 8; i++)
+ atf.connectPoints[i] = B_LITTLE16(atf.connectPoints[i]);
+#endif
+ // if (aimHeight.signature != '\x1aMED' && aimHeight.signature != '\x1aMDE')
+ if (atf.signature != 0x1a4d4544 && atf.signature != 0x1a4d4445)
+ return 0;
+ m_bLegacy = atf.signature == 0x1a4d4544;
+ if (m_bLegacy)
+ {
+ GAMEOPTIONSLEGACY gameOptions;
+ if (BloodVersion != atf.nVersion)
+ return 0;
+ kread(hPFile, &gameOptions, sizeof(GAMEOPTIONSLEGACY));
+ ReadGameOptionsLegacy(m_gameOptions, gameOptions);
+ }
+ else
+ {
+ if (BYTEVERSION != atf.nVersion)
+ return 0;
+ kread(hPFile, &m_gameOptions, sizeof(GAMEOPTIONS));
+ }
+#if B_BIG_ENDIAN == 1
+ m_gameOptions.nEpisode = B_LITTLE32(m_gameOptions.nEpisode);
+ m_gameOptions.nLevel = B_LITTLE32(m_gameOptions.nLevel);
+ m_gameOptions.nTrackNumber = B_LITTLE32(m_gameOptions.nTrackNumber);
+ m_gameOptions.nSaveGameSlot = B_LITTLE16(m_gameOptions.nSaveGameSlot);
+ m_gameOptions.picEntry = B_LITTLE32(m_gameOptions.picEntry);
+ m_gameOptions.uMapCRC = B_LITTLE32(m_gameOptions.uMapCRC);
+ m_gameOptions.uGameFlags = B_LITTLE32(m_gameOptions.uGameFlags);
+ m_gameOptions.uNetGameFlags = B_LITTLE32(m_gameOptions.uNetGameFlags);
+ m_gameOptions.nMonsterRespawnTime = B_LITTLE32(m_gameOptions.nMonsterRespawnTime);
+ m_gameOptions.nWeaponRespawnTime = B_LITTLE32(m_gameOptions.nWeaponRespawnTime);
+ m_gameOptions.nItemRespawnTime = B_LITTLE32(m_gameOptions.nItemRespawnTime);
+ m_gameOptions.nSpecialRespawnTime = B_LITTLE32(m_gameOptions.nSpecialRespawnTime);
+#endif
+ at0 = 0;
+ at1 = 1;
+ return 1;
+}
+
+void CDemo::ProcessKeys(void)
+{
+ switch (gInputMode)
+ {
+ case INPUT_MODE_1:
+ gGameMenuMgr.Process();
+ break;
+ case INPUT_MODE_2:
+ gPlayerMsg.ProcessKeys();
+ break;
+ case INPUT_MODE_0:
+ {
+ char nKey;
+ while ((nKey = keyGetScan()) != 0)
+ {
+ char UNUSED(alt) = keystatus[0x38] | keystatus[0xb8];
+ char UNUSED(ctrl) = keystatus[0x1d] | keystatus[0x9d];
+ switch (nKey)
+ {
+ case 1:
+ if (!CGameMenuMgr::m_bActive)
+ {
+ gGameMenuMgr.Push(&menuMain, -1);
+ at2 = 1;
+ }
+ break;
+ case 0x58:
+ gViewIndex = connectpoint2[gViewIndex];
+ if (gViewIndex == -1)
+ gViewIndex = connecthead;
+ gView = &gPlayer[gViewIndex];
+ break;
+ }
+ }
+ break;
+ default:
+ gInputMode = INPUT_MODE_0;
+ break;
+ }
+ }
+}
+
+void CDemo::Playback(void)
+{
+ CONTROL_BindsEnabled = false;
+ ready2send = 0;
+ int v4 = 0;
+ if (!CGameMenuMgr::m_bActive)
+ {
+ gGameMenuMgr.Push(&menuMain, -1);
+ at2 = 1;
+ }
+ gNetFifoClock = gGameClock;
+ gViewMode = 3;
+_DEMOPLAYBACK:
+ while (at1 && !gQuitGame)
+ {
+ if (handleevents() && quitevent)
+ {
+ KB_KeyDown[sc_Escape] = 1;
+ quitevent = 0;
+ }
+ MUSIC_Update();
+ while (gGameClock >= gNetFifoClock && !gQuitGame)
+ {
+ if (!v4)
+ {
+ viewResizeView(gViewSize);
+ viewSetMessage("");
+ gNetPlayers = atf.nNetPlayers;
+ atb = atf.nInputCount;
+ myconnectindex = atf.nMyConnectIndex;
+ connecthead = atf.nConnectHead;
+ for (int i = 0; i < 8; i++)
+ connectpoint2[i] = atf.connectPoints[i];
+ memset(gNetFifoHead, 0, sizeof(gNetFifoHead));
+ gNetFifoTail = 0;
+ //memcpy(connectpoint2, aimHeight.connectPoints, sizeof(aimHeight.connectPoints));
+ memcpy(&gGameOptions, &m_gameOptions, sizeof(GAMEOPTIONS));
+ gSkill = gGameOptions.nDifficulty;
+ for (int i = 0; i < 8; i++)
+ playerInit(i, 0);
+ StartLevel(&gGameOptions);
+ for (int i = 0; i < 8; i++)
+ {
+ gProfile[i].nAutoAim = 1;
+ gProfile[i].nWeaponSwitch = 1;
+ }
+ }
+ ready2send = 0;
+ OSD_DispatchQueued();
+ if (!gDemo.at1)
+ break;
+ ProcessKeys();
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if ((v4&1023) == 0)
+ {
+ unsigned int nSize = atb-v4;
+ if (nSize > kInputBufferSize)
+ nSize = kInputBufferSize;
+ ReadInput(nSize);
+ }
+ memcpy(&gFifoInput[gNetFifoHead[p]&255], &at1aa[v4&1023], sizeof(GINPUT));
+ gNetFifoHead[p]++;
+ v4++;
+ if (v4 >= atf.nInputCount)
+ {
+ ready2send = 0;
+ if (at59ef != 1)
+ {
+ v4 = 0;
+ Close();
+ NextDemo();
+ gNetFifoClock = gGameClock;
+ goto _DEMOPLAYBACK;
+ }
+ else
+ {
+ int const nOffset = sizeof(DEMOHEADER)+(m_bLegacy ? sizeof(GAMEOPTIONSLEGACY) : sizeof(GAMEOPTIONS));
+ klseek(hPFile, nOffset, SEEK_SET);
+ v4 = 0;
+ }
+ }
+ }
+ gNetFifoClock += 4;
+ if (!gQuitGame)
+ ProcessFrame();
+ ready2send = 0;
+ }
+ if (viewFPSLimit())
+ {
+ viewDrawScreen();
+ if (gInputMode == INPUT_MODE_1 && CGameMenuMgr::m_bActive)
+ gGameMenuMgr.Draw();
+ }
+ if (TestBitString(gotpic, 2342))
+ {
+ FireProcess();
+ ClearBitString(gotpic, 2342);
+ }
+ }
+ Close();
+}
+
+void CDemo::StopPlayback(void)
+{
+ at1 = 0;
+}
+
+void CDemo::LoadDemoInfo(void)
+{
+ auto pDemo = &pFirstDemo;
+ const int opsm = pathsearchmode;
+ at59ef = 0;
+ pathsearchmode = 0;
+ char zFN[BMAX_PATH];
+ Bsnprintf(zFN, BMAX_PATH, "%s*.dem", BloodIniPre);
+ auto pList = klistpath("/", zFN, CACHE1D_FIND_FILE);
+ auto pIterator = pList;
+ while (pIterator != NULL)
+ {
+ int hFile = kopen4loadfrommod(pIterator->name, 0);
+ if (hFile == -1)
+ ThrowError("Error loading demo file header.");
+ kread(hFile, &atf, sizeof(atf));
+ kclose(hFile);
+#if B_BIG_ENDIAN == 1
+ atf.signature = B_LITTLE32(atf.signature);
+ atf.nVersion = B_LITTLE16(atf.nVersion);
+#endif
+ if ((atf.signature == 0x1a4d4544 /* '\x1aMED' */&& atf.nVersion == BloodVersion)
+ || (atf.signature == 0x1a4d4445 /* '\x1aMDE' */ && atf.nVersion == BYTEVERSION))
+ {
+ *pDemo = new DEMOCHAIN;
+ (*pDemo)->pNext = NULL;
+ Bstrncpy((*pDemo)->zName, pIterator->name, BMAX_PATH);
+ at59ef++;
+ pDemo = &(*pDemo)->pNext;
+ }
+ pIterator = pIterator->next;
+ }
+ klistfree(pList);
+ pathsearchmode = opsm;
+ pCurrentDemo = pFirstDemo;
+}
+
+void CDemo::NextDemo(void)
+{
+ pCurrentDemo = pCurrentDemo->pNext ? pCurrentDemo->pNext : pFirstDemo;
+ SetupPlayback(NULL);
+}
+
+const int nInputSize = 17;
+const int nInputSizeLegacy = 22;
+
+void CDemo::FlushInput(int nCount)
+{
+ char pBuffer[nInputSize*kInputBufferSize];
+ BitWriter bitWriter(pBuffer, sizeof(pBuffer));
+ for (int i = 0; i < nCount; i++)
+ {
+ GINPUT *pInput = &at1aa[i];
+ bitWriter.writeBit(pInput->syncFlags.buttonChange);
+ bitWriter.writeBit(pInput->syncFlags.keyChange);
+ bitWriter.writeBit(pInput->syncFlags.useChange);
+ bitWriter.writeBit(pInput->syncFlags.weaponChange);
+ bitWriter.writeBit(pInput->syncFlags.mlookChange);
+ bitWriter.writeBit(pInput->syncFlags.run);
+ bitWriter.write(pInput->forward, 16);
+ bitWriter.write(pInput->q16turn, 32);
+ bitWriter.write(pInput->strafe, 16);
+ bitWriter.writeBit(pInput->buttonFlags.jump);
+ bitWriter.writeBit(pInput->buttonFlags.crouch);
+ bitWriter.writeBit(pInput->buttonFlags.shoot);
+ bitWriter.writeBit(pInput->buttonFlags.shoot2);
+ bitWriter.writeBit(pInput->buttonFlags.lookUp);
+ bitWriter.writeBit(pInput->buttonFlags.lookDown);
+ bitWriter.writeBit(pInput->keyFlags.action);
+ bitWriter.writeBit(pInput->keyFlags.jab);
+ bitWriter.writeBit(pInput->keyFlags.prevItem);
+ bitWriter.writeBit(pInput->keyFlags.nextItem);
+ bitWriter.writeBit(pInput->keyFlags.useItem);
+ bitWriter.writeBit(pInput->keyFlags.prevWeapon);
+ bitWriter.writeBit(pInput->keyFlags.nextWeapon);
+ bitWriter.writeBit(pInput->keyFlags.holsterWeapon);
+ bitWriter.writeBit(pInput->keyFlags.lookCenter);
+ bitWriter.writeBit(pInput->keyFlags.lookLeft);
+ bitWriter.writeBit(pInput->keyFlags.lookRight);
+ bitWriter.writeBit(pInput->keyFlags.spin180);
+ bitWriter.writeBit(pInput->keyFlags.pause);
+ bitWriter.writeBit(pInput->keyFlags.quit);
+ bitWriter.writeBit(pInput->keyFlags.restart);
+ bitWriter.writeBit(pInput->useFlags.useBeastVision);
+ bitWriter.writeBit(pInput->useFlags.useCrystalBall);
+ bitWriter.writeBit(pInput->useFlags.useJumpBoots);
+ bitWriter.writeBit(pInput->useFlags.useMedKit);
+ bitWriter.write(pInput->newWeapon, 8);
+ bitWriter.write(pInput->q16mlook, 32);
+ bitWriter.skipBits(1);
+ }
+ fwrite(pBuffer, 1, nInputSize*nCount, hRFile);
+}
+
+void CDemo::ReadInput(int nCount)
+{
+ if (m_bLegacy)
+ {
+ char pBuffer[nInputSizeLegacy*kInputBufferSize];
+ kread(hPFile, pBuffer, nInputSizeLegacy*nCount);
+ BitReader bitReader(pBuffer, sizeof(pBuffer));
+ memset(at1aa, 0, nCount * sizeof(GINPUT));
+ for (int i = 0; i < nCount; i++)
+ {
+ GINPUT *pInput = &at1aa[i];
+ pInput->syncFlags.buttonChange = bitReader.readBit();
+ pInput->syncFlags.keyChange = bitReader.readBit();
+ pInput->syncFlags.useChange = bitReader.readBit();
+ pInput->syncFlags.weaponChange = bitReader.readBit();
+ pInput->syncFlags.mlookChange = bitReader.readBit();
+ pInput->syncFlags.run = bitReader.readBit();
+ bitReader.skipBits(26);
+ pInput->forward = bitReader.readSigned(8) << 8;
+ pInput->q16turn = fix16_from_int(bitReader.readSigned(16) >> 2);
+ pInput->strafe = bitReader.readSigned(8) << 8;
+ pInput->buttonFlags.jump = bitReader.readBit();
+ pInput->buttonFlags.crouch = bitReader.readBit();
+ pInput->buttonFlags.shoot = bitReader.readBit();
+ pInput->buttonFlags.shoot2 = bitReader.readBit();
+ pInput->buttonFlags.lookUp = bitReader.readBit();
+ pInput->buttonFlags.lookDown = bitReader.readBit();
+ bitReader.skipBits(26);
+ pInput->keyFlags.action = bitReader.readBit();
+ pInput->keyFlags.jab = bitReader.readBit();
+ pInput->keyFlags.prevItem = bitReader.readBit();
+ pInput->keyFlags.nextItem = bitReader.readBit();
+ pInput->keyFlags.useItem = bitReader.readBit();
+ pInput->keyFlags.prevWeapon = bitReader.readBit();
+ pInput->keyFlags.nextWeapon = bitReader.readBit();
+ pInput->keyFlags.holsterWeapon = bitReader.readBit();
+ pInput->keyFlags.lookCenter = bitReader.readBit();
+ pInput->keyFlags.lookLeft = bitReader.readBit();
+ pInput->keyFlags.lookRight = bitReader.readBit();
+ pInput->keyFlags.spin180 = bitReader.readBit();
+ pInput->keyFlags.pause = bitReader.readBit();
+ pInput->keyFlags.quit = bitReader.readBit();
+ pInput->keyFlags.restart = bitReader.readBit();
+ bitReader.skipBits(17);
+ pInput->useFlags.useBeastVision = bitReader.readBit();
+ pInput->useFlags.useCrystalBall = bitReader.readBit();
+ pInput->useFlags.useJumpBoots = bitReader.readBit();
+ pInput->useFlags.useMedKit = bitReader.readBit();
+ bitReader.skipBits(28);
+ pInput->newWeapon = bitReader.readUnsigned(8);
+ int mlook = bitReader.readSigned(8);
+ pInput->q16mlook = fix16_from_int(mlook / 4);
+ }
+ }
+ else
+ {
+ char pBuffer[nInputSize*kInputBufferSize];
+ kread(hPFile, pBuffer, nInputSize*nCount);
+ BitReader bitReader(pBuffer, sizeof(pBuffer));
+ memset(at1aa, 0, nCount * sizeof(GINPUT));
+ for (int i = 0; i < nCount; i++)
+ {
+ GINPUT *pInput = &at1aa[i];
+ pInput->syncFlags.buttonChange = bitReader.readBit();
+ pInput->syncFlags.keyChange = bitReader.readBit();
+ pInput->syncFlags.useChange = bitReader.readBit();
+ pInput->syncFlags.weaponChange = bitReader.readBit();
+ pInput->syncFlags.mlookChange = bitReader.readBit();
+ pInput->syncFlags.run = bitReader.readBit();
+ pInput->forward = bitReader.readSigned(16);
+ pInput->q16turn = bitReader.readSigned(32);
+ pInput->strafe = bitReader.readSigned(16);
+ pInput->buttonFlags.jump = bitReader.readBit();
+ pInput->buttonFlags.crouch = bitReader.readBit();
+ pInput->buttonFlags.shoot = bitReader.readBit();
+ pInput->buttonFlags.shoot2 = bitReader.readBit();
+ pInput->buttonFlags.lookUp = bitReader.readBit();
+ pInput->buttonFlags.lookDown = bitReader.readBit();
+ pInput->keyFlags.action = bitReader.readBit();
+ pInput->keyFlags.jab = bitReader.readBit();
+ pInput->keyFlags.prevItem = bitReader.readBit();
+ pInput->keyFlags.nextItem = bitReader.readBit();
+ pInput->keyFlags.useItem = bitReader.readBit();
+ pInput->keyFlags.prevWeapon = bitReader.readBit();
+ pInput->keyFlags.nextWeapon = bitReader.readBit();
+ pInput->keyFlags.holsterWeapon = bitReader.readBit();
+ pInput->keyFlags.lookCenter = bitReader.readBit();
+ pInput->keyFlags.lookLeft = bitReader.readBit();
+ pInput->keyFlags.lookRight = bitReader.readBit();
+ pInput->keyFlags.spin180 = bitReader.readBit();
+ pInput->keyFlags.pause = bitReader.readBit();
+ pInput->keyFlags.quit = bitReader.readBit();
+ pInput->keyFlags.restart = bitReader.readBit();
+ pInput->useFlags.useBeastVision = bitReader.readBit();
+ pInput->useFlags.useCrystalBall = bitReader.readBit();
+ pInput->useFlags.useJumpBoots = bitReader.readBit();
+ pInput->useFlags.useMedKit = bitReader.readBit();
+ pInput->newWeapon = bitReader.readUnsigned(8);
+ pInput->q16mlook = bitReader.readSigned(32);
+ bitReader.skipBits(1);
+ }
+ }
+}
diff --git a/source/blood/src/demo.h b/source/blood/src/demo.h
new file mode 100644
index 000000000..03f0a6729
--- /dev/null
+++ b/source/blood/src/demo.h
@@ -0,0 +1,110 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "controls.h"
+#include "levels.h"
+
+#define kInputBufferSize 1024
+
+#pragma pack(push, 1)
+
+struct GAMEOPTIONSLEGACY {
+ char nGameType;
+ char nDifficulty;
+ int nEpisode;
+ int nLevel;
+ char zLevelName[144];
+ char zLevelSong[144];
+ int nTrackNumber; //at12a;
+ char szSaveGameName[16];
+ char szUserGameName[16];
+ short nSaveGameSlot;
+ int picEntry;
+ unsigned int uMapCRC;
+ char nMonsterSettings;
+ int uGameFlags;
+ int uNetGameFlags;
+ char nWeaponSettings;
+ char nItemSettings;
+ char nRespawnSettings;
+ char nTeamSettings;
+ int nMonsterRespawnTime;
+ int nWeaponRespawnTime;
+ int nItemRespawnTime;
+ int nSpecialRespawnTime;
+};
+
+struct DEMOHEADER
+{
+ int signature;
+ short nVersion;
+ int nBuild;
+ int nInputCount;
+ int nNetPlayers;
+ short nMyConnectIndex;
+ short nConnectHead;
+ short connectPoints[8];
+};
+
+#pragma pack(pop)
+
+struct DEMOCHAIN
+{
+ DEMOCHAIN *pNext;
+ char zName[BMAX_PATH];
+};
+
+class CDemo {
+public:
+ CDemo();
+ ~CDemo();
+ bool Create(const char *);
+ void Write(GINPUT *);
+ void Close(void);
+ bool SetupPlayback(const char *);
+ void ProcessKeys(void);
+ void Playback(void);
+ void StopPlayback(void);
+ void LoadDemoInfo(void);
+ void NextDemo(void);
+ void FlushInput(int nCount);
+ void ReadInput(int nCount);
+ bool at0; // record
+ bool at1; // playback
+ bool m_bLegacy;
+ char at2;
+ int at3;
+ int hPFile;
+ FILE *hRFile;
+ int atb;
+ DEMOHEADER atf;
+ GAMEOPTIONS m_gameOptions;
+ GINPUT at1aa[kInputBufferSize];
+ const char **pzDemoFile;
+ DEMOCHAIN *pFirstDemo;
+ DEMOCHAIN *pCurrentDemo;
+ int at59ef;
+};
+
+extern CDemo gDemo;
diff --git a/source/blood/src/dude.cpp b/source/blood/src/dude.cpp
new file mode 100644
index 000000000..a282da9c1
--- /dev/null
+++ b/source/blood/src/dude.cpp
@@ -0,0 +1,1727 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "common.h"
+#include "blood.h"
+#include "dude.h"
+
+DUDEINFO dudeInfo[kDudeMax-kDudeBase] =
+{
+ {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4096, //seqStartId
+ 40, // startHp
+ 70, // mass
+ 1200, // ???
+ 48, // clipdist
+ 41, // eye height
+ 20, // aim height
+ 10240, // hear dist
+ 51200, // see dist
+ 512, // periphery
+ 0, // melee distance
+ 10, // flee health
+ 8, // hinder damage
+ 256, // change target chance
+ 16, // change target chance to same type
+ 32768, // alert chance
+ 1, // lockout
+ 46603, // front speed
+ 34952, // side speed
+ 13981, // back speed
+ 256, // ang speed
+ 15, -1, -1, // gib type
+ 256, 256, 96, 256, 256, 256, 192, // start damage
+ 0, 0, 0, 0, 0, 0, 0, // real damage
+ 0, // ???
+ 0 // ???
+ },
+ {
+ 11520,
+ 40,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 5,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 34952,
+ 34952,
+ 13981,
+ 256,
+ 15, -1, -1,
+ 256, 256, 128, 256, 256, 256, 192,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4352,
+ 60,
+ 70,
+ 1200,
+ 48,
+ 46,
+ 20,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 15, -1, -1,
+ 256, 256, 112, 256, 256, 256, 160,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4608,
+ 80,
+ 200,
+ 1200,
+ 48,
+ 128,
+ 20,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 23301,
+ 23301,
+ 13981,
+ 256,
+ 15, -1, -1,
+ 256, 256, 32, 128, 256, 64, 128,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4352,
+ 60,
+ 70,
+ 1200,
+ 48,
+ 46,
+ 20,
+ 5120,
+ 0,
+ 341,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 15, -1, -1,
+ 256, 256, 112, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4864,
+ 110,
+ 120,
+ 1200,
+ 64,
+ 13,
+ 5,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 25,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 46603,
+ 34952,
+ 23301,
+ 384,
+ 30, -1, -1,
+ 0, 128, 48, 208, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 5120,
+ 200,
+ 200,
+ 1200,
+ 84,
+ 13,
+ 5,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 20,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 46603,
+ 34952,
+ 23301,
+ 256,
+ 19, -1, -1,
+ 0, 0, 10, 10, 0, 128, 64,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 11008,
+ 100,
+ 200,
+ 1200,
+ 64,
+ 13,
+ 5,
+ 2048,
+ 5120,
+ 512,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1, -1, -1,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 11264,
+ 100,
+ 200,
+ 1200,
+ 64,
+ 13,
+ 5,
+ 2048,
+ 5120,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1, -1, -1,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 5376,
+ 100,
+ 70,
+ 1200,
+ 64,
+ 25,
+ 15,
+ 10240,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ -1, -1, -1,
+ 0, 0, 48, 0, 0, 16, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 5632,
+ 70,
+ 120,
+ 1200,
+ 80,
+ 6,
+ 0,
+ 10240,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 20,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 116508,
+ 81555,
+ 69905,
+ 384,
+ 29, -1, -1,
+ 48, 0, 48, 48, 256, 128, 192,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 5888,
+ 10,
+ 70,
+ 1200,
+ 32,
+ 0,
+ 0,
+ 5120,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 64, 256, 256, 256, 0, 64, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 6144,
+ 10,
+ 5,
+ 1200,
+ 32,
+ -5,
+ -5,
+ 5120,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 64, 256, 256, 96, 256, 64, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 6400,
+ 25,
+ 10,
+ 1200,
+ 32,
+ -5,
+ -5,
+ 5120,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 64, 128, 256, 96, 256, 64, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 6656,
+ 75,
+ 20,
+ 1200,
+ 32,
+ -5,
+ -5,
+ 5120,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 128, 256, 256, 96, 256, 64, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 6912,
+ 100,
+ 40,
+ 1200,
+ 32,
+ -5,
+ -5,
+ 5120,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 32, 16, 16, 16, 32, 32, 32,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 7168,
+ 50,
+ 200,
+ 1200,
+ 64,
+ 37,
+ 20,
+ 5120,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 48, 80, 64, 128, 0, 128, 48,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 7424,
+ 25,
+ 30,
+ 1200,
+ 32,
+ 4,
+ 0,
+ 5120,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 34952,
+ 23301,
+ 23301,
+ 128,
+ 7, -1, -1,
+ 256, 256, 256, 256, 0, 256, 192,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 7680,
+ 10,
+ 5,
+ 1200,
+ 32,
+ 2,
+ 0,
+ 10240,
+ 25600,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 23301,
+ 23301,
+ 13981,
+ 384,
+ 7, -1, -1,
+ 256, 256, 256, 256, 256, 64, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 7936,
+ 10,
+ 5,
+ 1200,
+ 32,
+ 3,
+ 0,
+ 12800,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 256, 256, 256, 256, 256, 128, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 8192,
+ 50,
+ 65535,
+ 1200,
+ 64,
+ 40,
+ 0,
+ 2048,
+ 11264,
+ 1024,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 384,
+ 7, -1, -1,
+ 160, 160, 128, 160, 0, 0, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 8448,
+ 10,
+ 65535,
+ 1200,
+ 32,
+ 0,
+ 0,
+ 2048,
+ 5120,
+ 1024,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 384,
+ 7, -1, -1,
+ 256, 256, 256, 80, 0, 0, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 8704,
+ 100,
+ 65535,
+ 1200,
+ 64,
+ 40,
+ 0,
+ 2048,
+ 15360,
+ 1024,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 384,
+ 7, -1, -1,
+ 96, 0, 128, 64, 256, 64, 160,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 8960,
+ 20,
+ 65535,
+ 1200,
+ 32,
+ 0,
+ 0,
+ 2048,
+ 5120,
+ 1024,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 384,
+ 7, -1, -1,
+ 128, 0, 128, 128, 0, 0, 128,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 9216,
+ 200,
+ 65535,
+ 1200,
+ 64,
+ 40,
+ 0,
+ 2048,
+ 51200,
+ 1024,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 7, -1, -1,
+ 128, 256, 128, 256, 128, 128, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 9472,
+ 50,
+ 65535,
+ 1200,
+ 32,
+ 0,
+ 0,
+ 2048,
+ 51200,
+ 1024,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 7, -1, -1,
+ 256, 256, 128, 256, 128, 128, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 9728,
+ 200,
+ 1000,
+ 1200,
+ 64,
+ 29,
+ 10,
+ 40960,
+ 102400,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 69905,
+ 58254,
+ 46603,
+ 384,
+ 7, -1, -1,
+ 16, 0, 16, 16, 0, 96, 48,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 9984,
+ 100,
+ 1000,
+ 1200,
+ 64,
+ 29,
+ 10,
+ 20480,
+ 51200,
+ 682,
+ 0,
+ 10,
+ 10,
+ 256,
+ 0,
+ 32768,
+ 0,
+ 58254,
+ 34952,
+ 25631,
+ 384,
+ 7, -1, -1,
+ 16, 0, 16, 16, 0, 96, 48,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 10240,
+ 32, // 800,
+ 1500,
+ 1200,
+ 128,
+ 0,
+ 0,
+ 25600,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 58254,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 3, 1, 4, 4, 0, 4, 3,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4096,
+ 25,
+ 20,
+ 1200,
+ 32,
+ 0,
+ 0,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 15, -1, -1,
+ 256, 256, 96, 256, 256, 256, 192,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12032,
+ 100,
+ 70,
+ 1200,
+ 48,
+ 0,
+ 16,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12544,
+ 25,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 10240,
+ 51200,
+ 341,
+ 0,
+ 100,
+ 100,
+ 0,
+ 0,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 160,
+ 7, 5, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4096,
+ 30,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 10240,
+ 51200,
+ 341,
+ 0,
+ 100,
+ 100,
+ 0,
+ 0,
+ 32768,
+ 0,
+ 46603,
+ 34952,
+ 13981,
+ 160,
+ 7, 5, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4352,
+ 12,
+ 70,
+ 1200,
+ 48,
+ 46,
+ 20,
+ 10240,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 160,
+ 7, 5, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4352,
+ 25,
+ 120,
+ 1200,
+ 48,
+ 44,
+ 20,
+ 10240,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 39612,
+ 27962,
+ 13981,
+ 100,
+ 7, 5, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4096,
+ 100,
+ 70,
+ 1200,
+ 64,
+ 38,
+ 20,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 64,
+ 15, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 4352,
+ 60,
+ 70,
+ 1200,
+ 48,
+ 46,
+ 20,
+ 5120,
+ 0,
+ 341,
+ 0,
+ 10,
+ 15,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 15, -1, -1,
+ 256, 256, 112, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12544,
+ 50,
+ 70,
+ 1200,
+ 48,
+ 46,
+ 20,
+ 2560,
+ 0,
+ 341,
+ 0,
+ 10,
+ 8,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 15, -1, -1,
+ 288, 288, 288, 288, 288, 288, 288,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 11520,
+ 25,
+ 70,
+ 1200,
+ 32,
+ -5,
+ 0,
+ 2048,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 0,
+ 0,
+ 0,
+ 64,
+ 7, 5, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 12800,
+ 40,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 8,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 46603,
+ 34952,
+ 13981,
+ 256,
+ 15, -1, -1,
+ 256, 256, 96, 160, 256, 256, 12,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 13056,
+ 40,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 8,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 46603,
+ 34952,
+ 13981,
+ 256,
+ 15, -1, -1,
+ 256, 160, 96, 64, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 13312,
+ 40,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 10240,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 12,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 46603,
+ 34952,
+ 13981,
+ 256,
+ 15, -1, -1,
+ 128, 128, 16, 16, 0, 64, 48,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 13568,
+ 10,
+ 5,
+ 1200,
+ 32,
+ 3,
+ 0,
+ 12800,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 160, 160, 160, 160, 256, 128, 288,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 10752,
+ 120,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 12800,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 116508,
+ 81555,
+ 69905,
+ 384,
+ 7, -1, -1,
+ 5, 5, 15, 8, 0, 15, 15,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 13568,
+ 10,
+ 5,
+ 1200,
+ 32,
+ 3,
+ 0,
+ 12800,
+ 51200,
+ 512,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 0,
+ 58254,
+ 46603,
+ 34952,
+ 384,
+ 7, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ {
+ 10752,
+ 25,
+ 70,
+ 1200,
+ 48,
+ 41,
+ 20,
+ 12800,
+ 51200,
+ 341,
+ 0,
+ 10,
+ 10,
+ 256,
+ 16,
+ 32768,
+ 1,
+ 116508,
+ 81555,
+ 69905,
+ 384,
+ 7, -1, -1,
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ //254 - kGDXUniversalCultist
+ {
+ 11520, // start sequence ID
+ 85, // start health
+ 75, // mass
+ 120,
+ 48, // clip distance
+ 48, // eye above z
+ 20,
+ 10240, // hear distance
+ 51200, // seeing distance
+ kAng120, // vision periphery
+ // 0,
+ 618, // melee distance
+ 5, // flee health
+ 12, // hinder damage
+ 0x0100, // change target chance
+ 0x0010, // change target to kin chance
+ 0x8000, // alertChance
+ 0, // lockout
+ 46603, // frontSpeed
+ 34952, // sideSpeed
+ 13981, // backSpeed
+ 256, // angSpeed
+ // 0,
+ 7, -1, 18, // nGibType
+ 256, 256, 128, 256, 256, 256, 192,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+ //255 - kGDXGenDudeBurning
+ {
+ 4096, // start sequence ID
+ 25, // start health
+ 5, // mass
+ 120,
+ 48, // clip distance
+ 41, // eye above z
+ 20,
+ 12800, // hear distance
+ 51200, // seeing distance
+ kAng60, // vision periphery
+ // 0,
+ 0, // melee distance
+ 10, // flee health
+ 10, // hinder damage
+ 0x0100, // change target chance
+ 0x0010, // change target to kin chance
+ 0x8000, // alertChance
+ true, // lockout
+ 58254, // frontSpeed
+ 46603, // sideSpeed
+ 34952, // backSpeed
+ 384, // angSpeed
+ // 0,
+ 7, -1, -1, // nGibType
+ 256, 256, 256, 256, 256, 256, 256,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ }
+};
+
+DUDEINFO gPlayerTemplate[4] =
+{
+ // normal human
+ {
+ 0x2f00,
+ 100,
+ 70,
+ 1200,
+ 0x30,
+ 0,
+ 0x10,
+ 0x800,
+ 0xc800,
+ 0x155,
+ 0,
+ 10,
+ 10,
+ 0x100,
+ 0x10,
+ 0x8000,
+ 0x1,
+ 0,
+ 0,
+ 0,
+ 0x40,
+ 15, -1, -1,
+ 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x120,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+
+ // normal beast
+ {
+ 0x2900,
+ 100,
+ 70,
+ 1200,
+ 0x30,
+ 0,
+ 0x14,
+ 0x800,
+ 0xc800,
+ 0x155,
+ 0,
+ 10,
+ 10,
+ 0x100,
+ 0x10,
+ 0x8000,
+ 0x1,
+ 0,
+ 0,
+ 0,
+ 0x40,
+ 7, -1, -1,
+ 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x120,
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+
+ // shrink human
+ {
+ 12032,
+ 100,
+ 10, // mass
+ 1200,
+ 16, // clipdist
+ 0,
+ 0x10,
+ 0x800,
+ 0xc800,
+ 0x155,
+ 0,
+ 10,
+ 10,
+ 0x100,
+ 0x10,
+ 0x8000,
+ 0x1,
+ 0,
+ 0,
+ 0,
+ 0x40,
+ 15, -1, -1, // gib type
+ 1024, 1024, 1024, 1024, 256, 1024, 1024, //damage shift
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+
+ // grown human
+ {
+ 12032,
+ 100,
+ 1100, // mass
+ 1200,
+ 100, // clipdist
+ 0,
+ 0x10,
+ 0x800,
+ 0xc800,
+ 0x155,
+ 0,
+ 10,
+ 10,
+ 0x100,
+ 0x10,
+ 0x8000,
+ 0x1,
+ 0,
+ 0,
+ 0,
+ 0x40,
+ 15, 7, 7, // gib type
+ 64, 64, 64, 64, 256, 64, 64, // damage shift
+ 0, 0, 0, 0, 0, 0, 0,
+ 0,
+ 0
+ },
+};
diff --git a/source/blood/src/dude.h b/source/blood/src/dude.h
new file mode 100644
index 000000000..5f317e520
--- /dev/null
+++ b/source/blood/src/dude.h
@@ -0,0 +1,56 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "blood.h"
+// By NoOne: renamed dude struct
+struct DUDEINFO {
+ short seqStartID; // seq
+ short startHealth; // health
+ unsigned short mass; // mass
+ int at6; // unused?
+ unsigned char clipdist; // clipdist
+ int eyeHeight;
+ int aimHeight; // used by just Cerberus
+ int hearDist; // hear radius
+ int seeDist; // sight radius
+ int periphery; // periphery
+ int meleeDist; // unused?
+ int fleeHealth; // at which hp level enemy will turn in burning dude
+ int hinderDamage; // recoil damage
+ int changeTarget; // chance to change target when attacked someone else
+ int changeTargetKin; // chance to change target when attacked by same type
+ int alertChance;
+ char lockOut; // indicates if this dude can trigger something via trigger flags
+ int frontSpeed; // acceleration
+ int sideSpeed; // dodge
+ int backSpeed; // backward speed (unused)
+ int angSpeed; // turn speed
+ int nGibType[3]; // which gib used when explode dude
+ int startDamage[7]; // start damage shift
+ int at70[7]; // real damage? Hmm?
+ int at8c; // unused ?
+ int at90; // unused ?
+};
+
+extern DUDEINFO dudeInfo[kDudeMax-kDudeBase];
+extern DUDEINFO gPlayerTemplate[4];
\ No newline at end of file
diff --git a/source/blood/src/endgame.cpp b/source/blood/src/endgame.cpp
new file mode 100644
index 000000000..07f09e778
--- /dev/null
+++ b/source/blood/src/endgame.cpp
@@ -0,0 +1,277 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "common.h"
+#include "mmulti.h"
+#include "fx_man.h"
+#include "common_game.h"
+#include "blood.h"
+#include "endgame.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "menu.h"
+#include "network.h"
+#include "player.h"
+#include "sound.h"
+#include "view.h"
+
+CEndGameMgr::CEndGameMgr()
+{
+ at0 = 0;
+}
+
+void CEndGameMgr::Draw(void)
+{
+ viewLoadingScreenWide();
+ int nHeight;
+ gMenuTextMgr.GetFontInfo(1, NULL, NULL, &nHeight);
+ rotatesprite(160<<16, 20<<16, 65536, 0, 2038, -128, 0, 6, 0, 0, xdim-1, ydim-1);
+ int nY = 20 - nHeight / 2;
+ if (gGameOptions.nGameType == 0)
+ {
+ viewDrawText(1, "LEVEL STATS", 160, nY, -128, 0, 1, 0);
+ if (CCheatMgr::m_bPlayerCheated)
+ {
+ viewDrawText(3, ">>> YOU CHEATED! <<<", 160, 32, -128, 0, 1, 1);
+ }
+ gKillMgr.Draw();
+ gSecretMgr.Draw();
+ }
+ else
+ {
+ viewDrawText(1, "FRAG STATS", 160, nY, -128, 0, 1, 0);
+ gKillMgr.Draw();
+ }
+ if (/*dword_28E3D4 != 1 && */(gGameClock&32))
+ {
+ viewDrawText(3, "PRESS A KEY TO CONTINUE", 160, 134, -128, 0, 1, 1);
+ }
+}
+
+void CEndGameMgr::ProcessKeys(void)
+{
+ //if (dword_28E3D4 == 1)
+ //{
+ // if (gGameOptions.gameType >= 0 || numplayers > 1)
+ // netWaitForEveryone(0);
+ // Finish();
+ //}
+ //else
+ {
+ char ch = keyGetScan();
+ if (!ch)
+ return;
+ if (gGameOptions.nGameType > 0 || numplayers > 1)
+ netWaitForEveryone(0);
+ Finish();
+ }
+}
+
+extern void EndLevel(void);
+
+void CEndGameMgr::Setup(void)
+{
+ at1 = gInputMode;
+ gInputMode = INPUT_MODE_3;
+ at0 = 1;
+ EndLevel();
+ sndStartSample(268, 128, -1, 1);
+ keyFlushScans();
+}
+
+//int gNextLevel;
+
+extern int gInitialNetPlayers;
+extern bool gStartNewGame;
+
+void CEndGameMgr::Finish(void)
+{
+ levelSetupOptions(gGameOptions.nEpisode, gNextLevel);
+ gInitialNetPlayers = numplayers;
+ //if (FXDevice != -1)
+ FX_StopAllSounds();
+ sndKillAllSounds();
+ gStartNewGame = 1;
+ gInputMode = (INPUT_MODE)at1;
+ at0 = 0;
+}
+
+CKillMgr::CKillMgr()
+{
+ Clear();
+}
+
+void CKillMgr::SetCount(int nCount)
+{
+ at0 = nCount;
+}
+
+void CKillMgr::sub_263E0(int nCount)
+{
+ at0 += nCount;
+}
+
+void CKillMgr::AddKill(spritetype *pSprite)
+{
+ if (pSprite->statnum == 6 && pSprite->type != 219 && pSprite->type != 220 && pSprite->type != 245 && pSprite->type != 239)
+ at4++;
+}
+
+void CKillMgr::sub_2641C(void)
+{
+ at0 = 0;
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->type < kDudeBase || pSprite->type >= kDudeMax)
+ ThrowError("Non-enemy sprite (%d) in the enemy sprite list.", nSprite);
+ if (pSprite->statnum == 6 && pSprite->type != 219 && pSprite->type != 220 && pSprite->type != 245 && pSprite->type != 239)
+ at0++;
+ }
+}
+
+void CKillMgr::Draw(void)
+{
+ char pBuffer[40];
+ if (gGameOptions.nGameType == 0)
+ {
+ viewDrawText(1, "KILLS:", 75, 50, -128, 0, 0, 1);
+ sprintf(pBuffer, "%2d", at4);
+ viewDrawText(1, pBuffer, 160, 50, -128, 0, 0, 1);
+ viewDrawText(1, "OF", 190, 50, -128, 0, 0, 1);
+ sprintf(pBuffer, "%2d", at0);
+ viewDrawText(1, pBuffer, 220, 50, -128, 0, 0, 1);
+ }
+ else
+ {
+ viewDrawText(3, "#", 85, 35, -128, 0, 0, 1);
+ viewDrawText(3, "NAME", 100, 35, -128, 0, 0, 1);
+ viewDrawText(3, "FRAGS", 210, 35, -128, 0, 0, 1);
+ int nStart = 0;
+ int nEnd = gInitialNetPlayers;
+ //if (dword_28E3D4 == 1)
+ //{
+ // nStart++;
+ // nEnd++;
+ //}
+ for (int i = nStart; i < nEnd; i++)
+ {
+ sprintf(pBuffer, "%-2d", i);
+ viewDrawText(3, pBuffer, 85, 50+8*i, -128, 0, 0, 1);
+ sprintf(pBuffer, "%s", gProfile[i].name);
+ viewDrawText(3, pBuffer, 100, 50+8*i, -128, 0, 0, 1);
+ sprintf(pBuffer, "%d", gPlayer[i].at2c6);
+ viewDrawText(3, pBuffer, 210, 50+8*i, -128, 0, 0, 1);
+ }
+ }
+}
+
+void CKillMgr::Clear(void)
+{
+ at0 = at4 = 0;
+}
+
+CSecretMgr::CSecretMgr(void)
+{
+ Clear();
+}
+
+void CSecretMgr::SetCount(int nCount)
+{
+ at0 = nCount;
+}
+
+void CSecretMgr::Found(int nType)
+{
+ if (nType < 0)
+ ThrowError("Invalid secret type %d triggered.", nType);
+ if (nType == 0)
+ at4++;
+ else
+ at8++;
+ if (gGameOptions.nGameType == 0)
+ {
+ switch (Random(2))
+ {
+ case 0:
+ viewSetMessage("A secret is revealed.");
+ break;
+ case 1:
+ viewSetMessage("You found a secret.");
+ break;
+ }
+ }
+}
+
+void CSecretMgr::Draw(void)
+{
+ char pBuffer[40];
+ viewDrawText(1, "SECRETS:", 75, 70, -128, 0, 0, 1);
+ sprintf(pBuffer, "%2d", at4);
+ viewDrawText(1, pBuffer, 160, 70, -128, 0, 0, 1);
+ viewDrawText(1, "OF", 190, 70, -128, 0, 0, 1);
+ sprintf(pBuffer, "%2d", at0);
+ viewDrawText(1, pBuffer, 220, 70, -128, 0, 0, 1);
+ if (at8 > 0)
+ viewDrawText(1, "YOU FOUND A SUPER SECRET!", 160, 100, -128, 2, 1, 1);
+}
+
+void CSecretMgr::Clear(void)
+{
+ at0 = at4 = at8 = 0;
+}
+
+class EndGameLoadSave : public LoadSave {
+public:
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+void EndGameLoadSave::Load(void)
+{
+ Read(&gSecretMgr.at0, 4);
+ Read(&gSecretMgr.at4, 4);
+ Read(&gSecretMgr.at8, 4);
+ Read(&gKillMgr.at0, 4);
+ Read(&gKillMgr.at4, 4);
+}
+
+void EndGameLoadSave::Save(void)
+{
+ Write(&gSecretMgr.at0, 4);
+ Write(&gSecretMgr.at4, 4);
+ Write(&gSecretMgr.at8, 4);
+ Write(&gKillMgr.at0, 4);
+ Write(&gKillMgr.at4, 4);
+}
+
+CEndGameMgr gEndGameMgr;
+CSecretMgr gSecretMgr;
+CKillMgr gKillMgr;
+static EndGameLoadSave *myLoadSave;
+
+void EndGameLoadSaveConstruct(void)
+{
+ myLoadSave = new EndGameLoadSave();
+}
diff --git a/source/blood/src/endgame.h b/source/blood/src/endgame.h
new file mode 100644
index 000000000..8c66fdc53
--- /dev/null
+++ b/source/blood/src/endgame.h
@@ -0,0 +1,62 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common_game.h"
+
+class CEndGameMgr {
+public:
+ char at0;
+ char at1;
+ CEndGameMgr();
+ void Setup(void);
+ void ProcessKeys(void);
+ void Draw(void);
+ void Finish(void);
+};
+
+class CKillMgr {
+public:
+ int at0, at4;
+ CKillMgr();
+ void SetCount(int);
+ void sub_263E0(int);
+ void AddKill(spritetype *pSprite);
+ void sub_2641C(void);
+ void Clear(void);
+ void Draw(void);
+};
+
+class CSecretMgr {
+public:
+ int at0, at4, at8;
+ CSecretMgr();
+ void SetCount(int);
+ void Found(int);
+ void Clear(void);
+ void Draw(void);
+};
+
+extern CEndGameMgr gEndGameMgr;
+extern CSecretMgr gSecretMgr;
+extern CKillMgr gKillMgr;
diff --git a/source/blood/src/eventq.cpp b/source/blood/src/eventq.cpp
new file mode 100644
index 000000000..9a348539b
--- /dev/null
+++ b/source/blood/src/eventq.cpp
@@ -0,0 +1,611 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include "build.h"
+#include "common_game.h"
+
+#include "callback.h"
+#include "db.h"
+#include "eventq.h"
+#include "globals.h"
+#include "loadsave.h"
+#include "pqueue.h"
+#include "triggers.h"
+
+class EventQueue
+{
+public:
+ PriorityQueue* PQueue;
+ EventQueue()
+ {
+ PQueue = NULL;
+ }
+ bool IsNotEmpty(unsigned int nTime)
+ {
+ return PQueue->Size() > 0 && nTime >= PQueue->LowestPriority();
+ }
+ EVENT ERemove(void)
+ {
+ unsigned int node = PQueue->Remove();
+ return *(EVENT*)&node;
+ }
+ void Kill(int, int);
+ void Kill(int, int, CALLBACK_ID);
+};
+
+EventQueue eventQ;
+void EventQueue::Kill(int a1, int a2)
+{
+ EVENT evn = { (unsigned int)a1, (unsigned int)a2, 0, 0 };
+ //evn.at0_0 = a1;
+ //evn.at1_5 = a2;
+
+ short vs = *(short*)&evn;
+ PQueue->Kill([=](unsigned int nItem)->bool {return !memcmp(&nItem, &vs, 2); });
+}
+
+void EventQueue::Kill(int a1, int a2, CALLBACK_ID a3)
+{
+ EVENT evn = { (unsigned int)a1, (unsigned int)a2, kCommandCallback, (unsigned int)a3 };
+ unsigned int vc = *(unsigned int*)&evn;
+ PQueue->Kill([=](unsigned int nItem)->bool {return nItem == vc; });
+}
+
+//struct RXBUCKET
+//{
+// unsigned int at0_0 : 13;
+// unsigned int at1_5 : 3;
+//};
+
+RXBUCKET rxBucket[kMaxChannels+1];
+
+int GetBucketChannel(const RXBUCKET *pRX)
+{
+ switch (pRX->type)
+ {
+ case 6:
+ {
+ int nIndex = pRX->index;
+ int nXIndex = sector[nIndex].extra;
+ dassert(nXIndex > 0);
+ return xsector[nXIndex].rxID;
+ }
+ case 0:
+ {
+ int nIndex = pRX->index;
+ int nXIndex = wall[nIndex].extra;
+ dassert(nXIndex > 0);
+ return xwall[nXIndex].rxID;
+ }
+ case 3:
+ {
+ int nIndex = pRX->index;
+ int nXIndex = sprite[nIndex].extra;
+ dassert(nXIndex > 0);
+ return xsprite[nXIndex].rxID;
+ }
+ default:
+ ThrowError("Unexpected rxBucket type %d, index %d", pRX->type, pRX->index);
+ break;
+ }
+ return 0;
+}
+
+#if 0
+int CompareChannels(const void *a, const void *b)
+{
+ return GetBucketChannel((const RXBUCKET*)a)-GetBucketChannel((const RXBUCKET*)b);
+}
+#else
+static int CompareChannels(RXBUCKET *a, RXBUCKET *b)
+{
+ return GetBucketChannel(a) - GetBucketChannel(b);
+}
+#endif
+
+static RXBUCKET *SortGetMiddle(RXBUCKET *a1, RXBUCKET *a2, RXBUCKET *a3)
+{
+ if (CompareChannels(a1, a2) > 0)
+ {
+ if (CompareChannels(a1, a3) > 0)
+ {
+ if (CompareChannels(a2, a3) > 0)
+ return a2;
+ return a3;
+ }
+ return a1;
+ }
+ else
+ {
+ if (CompareChannels(a1, a3) < 0)
+ {
+ if (CompareChannels(a2, a3) > 0)
+ return a3;
+ return a2;
+ }
+ return a1;
+ }
+}
+
+static void SortSwap(RXBUCKET *a, RXBUCKET *b)
+{
+ RXBUCKET t = *a;
+ *a = *b;
+ *b = t;
+}
+
+static void SortRXBucket(int nCount)
+{
+ RXBUCKET *v144[32];
+ int vc4[32];
+ int v14 = 0;
+ RXBUCKET *pArray = rxBucket;
+ while (true)
+ {
+ while (nCount > 1)
+ {
+ if (nCount < 16)
+ {
+ for (int nDist = 3; nDist > 0; nDist -= 2)
+ {
+ for (RXBUCKET *pI = pArray+nDist; pI < pArray+nCount; pI += nDist)
+ {
+ for (RXBUCKET *pJ = pI; pJ > pArray && CompareChannels(pJ-nDist, pJ) > 0; pJ -= nDist)
+ {
+ SortSwap(pJ, pJ-nDist);
+ }
+ }
+ }
+ break;
+ }
+ RXBUCKET *v30, *vdi, *vsi;
+ vdi = pArray + nCount / 2;
+ if (nCount > 29)
+ {
+ v30 = pArray;
+ vsi = pArray + nCount-1;
+ if (nCount > 42)
+ {
+ int v20 = nCount / 8;
+ v30 = SortGetMiddle(v30, v30+v20, v30+v20*2);
+ vdi = SortGetMiddle(vdi-v20, vdi, vdi+v20);
+ vsi = SortGetMiddle(vsi-v20*2, vsi-v20, vsi);
+ }
+ vdi = SortGetMiddle(v30, vdi, vsi);
+ }
+ RXBUCKET v44 = *vdi;
+ RXBUCKET *vc = pArray;
+ RXBUCKET *v8 = pArray+nCount-1;
+ RXBUCKET *vbx = vc;
+ RXBUCKET *v4 = v8;
+ while (true)
+ {
+ while (vbx <= v4)
+ {
+ int nCmp = CompareChannels(vbx, &v44);
+ if (nCmp > 0)
+ break;
+ if (nCmp == 0)
+ {
+ SortSwap(vbx, vc);
+ vc++;
+ }
+ vbx++;
+ }
+ while (vbx <= v4)
+ {
+ int nCmp = CompareChannels(v4, &v44);
+ if (nCmp < 0)
+ break;
+ if (nCmp == 0)
+ {
+ SortSwap(v4, v8);
+ v8--;
+ }
+ v4--;
+ }
+ if (vbx > v4)
+ break;
+ SortSwap(vbx, v4);
+ v4--;
+ vbx++;
+ }
+ RXBUCKET *v2c = pArray+nCount;
+ int vt = ClipHigh(vbx-vc, vc-pArray);
+ for (int i = 0; i < vt; i++)
+ {
+ SortSwap(&vbx[i-vt], &pArray[i]);
+ }
+ vt = ClipHigh(v8-v4, v2c-v8-1);
+ for (int i = 0; i < vt; i++)
+ {
+ SortSwap(&v2c[i-vt], &vbx[i]);
+ }
+ int vvsi = v8-v4;
+ int vvdi = vbx-vc;
+ if (vvsi >= vvdi)
+ {
+ vc4[v14] = vvsi;
+ v144[v14] = v2c-vvsi;
+ nCount = vvdi;
+ v14++;
+ }
+ else
+ {
+ vc4[v14] = vvdi;
+ v144[v14] = pArray;
+ nCount = vvsi;
+ pArray = v2c - vvsi;
+ v14++;
+ }
+ }
+ if (v14 == 0)
+ return;
+ v14--;
+ pArray = v144[v14];
+ nCount = vc4[v14];
+ }
+}
+
+unsigned short bucketHead[1024+1];
+
+void evInit(void)
+{
+ if (eventQ.PQueue)
+ delete eventQ.PQueue;
+ if (VanillaMode())
+ eventQ.PQueue = new VanillaPriorityQueue();
+ else
+ eventQ.PQueue = new StdPriorityQueue();
+ eventQ.PQueue->Clear();
+ int nCount = 0;
+ for (int i = 0; i < numsectors; i++)
+ {
+ int nXSector = sector[i].extra;
+ if (nXSector >= kMaxXSectors)
+ ThrowError("Invalid xsector reference in sector %d", i);
+ if (nXSector > 0 && xsector[nXSector].rxID > 0)
+ {
+ dassert(nCount < kMaxChannels);
+ rxBucket[nCount].type = 6;
+ rxBucket[nCount].index = i;
+ nCount++;
+ }
+ }
+ for (int i = 0; i < numwalls; i++)
+ {
+ int nXWall = wall[i].extra;
+ if (nXWall >= kMaxXWalls)
+ ThrowError("Invalid xwall reference in wall %d", i);
+ if (nXWall > 0 && xwall[nXWall].rxID > 0)
+ {
+ dassert(nCount < kMaxChannels);
+ rxBucket[nCount].type = 0;
+ rxBucket[nCount].index = i;
+ nCount++;
+ }
+ }
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ if (sprite[i].statnum < kMaxStatus)
+ {
+ int nXSprite = sprite[i].extra;
+ if (nXSprite >= kMaxXSprites)
+ ThrowError("Invalid xsprite reference in sprite %d", i);
+ if (nXSprite > 0 && xsprite[nXSprite].rxID > 0)
+ {
+ dassert(nCount < kMaxChannels);
+ rxBucket[nCount].type = 3;
+ rxBucket[nCount].index = i;
+ nCount++;
+ }
+ }
+ }
+ SortRXBucket(nCount);
+ int i, j = 0;
+ for (i = 0; i < 1024; i++)
+ {
+ bucketHead[i] = j;
+ while(j < nCount && GetBucketChannel(&rxBucket[j]) == i)
+ j++;
+ }
+ bucketHead[i] = j;
+}
+
+char evGetSourceState(int nType, int nIndex)
+{
+ switch (nType)
+ {
+ case 6:
+ {
+ int nXIndex = sector[nIndex].extra;
+ dassert(nXIndex > 0 && nXIndex < kMaxXSectors);
+ return xsector[nXIndex].state;
+ }
+ case 0:
+ {
+ int nXIndex = wall[nIndex].extra;
+ dassert(nXIndex > 0 && nXIndex < kMaxXWalls);
+ return xwall[nXIndex].state;
+ }
+ case 3:
+ {
+ int nXIndex = sprite[nIndex].extra;
+ dassert(nXIndex > 0 && nXIndex < kMaxXSprites);
+ return xsprite[nXIndex].state;
+ }
+ }
+ return 0;
+}
+
+void evSend(int nIndex, int nType, int rxId, COMMAND_ID command)
+{
+ if (command == COMMAND_ID_2)
+ command = evGetSourceState(nType, nIndex) ? COMMAND_ID_1 : COMMAND_ID_0;
+ else if (command == COMMAND_ID_4)
+ command = evGetSourceState(nType, nIndex) ? COMMAND_ID_0 : COMMAND_ID_1;
+ EVENT evn;
+ evn.index = nIndex;
+ evn.type = nType;
+ evn.cmd = command;
+ if (rxId > 0)
+ {
+ switch (rxId)
+ {
+ case 7:
+ case 10:
+ break;
+ case 3:
+ if (command < COMMAND_ID_64)
+ ThrowError("Invalid TextOver command by xobject %d(type %d)", nIndex, nType);
+ trTextOver(command-COMMAND_ID_64);
+ return;
+ case 4:
+ levelEndLevel(0);
+ return;
+ case 5:
+ levelEndLevel(1);
+ return;
+ // By NoOne: finished level and load custom level ¹ via numbered command.
+ case kGDXChannelEndLevelCustom:
+ levelEndLevelCustom(command - 64);
+ return;
+ case 1:
+ if (command < COMMAND_ID_64)
+ ThrowError("Invalid SetupSecret command by xobject %d(type %d)", nIndex, nType);
+ levelSetupSecret(command - COMMAND_ID_64);
+ break;
+ case 2:
+ if (command < COMMAND_ID_64)
+ ThrowError("Invalid Secret command by xobject %d(type %d)", nIndex, nType);
+ levelTriggerSecret(command - COMMAND_ID_64);
+ break;
+ case 90:
+ case 91:
+ case 92:
+ case 93:
+ case 94:
+ case 95:
+ case 96:
+ case 97:
+ for (int nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->rxID == rxId)
+ trMessageSprite(nSprite, evn);
+ }
+ }
+ return;
+ case 80:
+ case 81:
+ for (int nSprite = headspritestat[3]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->rxID == rxId)
+ trMessageSprite(nSprite, evn);
+ }
+ }
+ return;
+ }
+ }
+ for (int i = bucketHead[rxId]; i < bucketHead[rxId+1]; i++)
+ {
+ if (evn.type != rxBucket[i].type || evn.index != rxBucket[i].index)
+ {
+ switch (rxBucket[i].type)
+ {
+ case 6:
+ trMessageSector(rxBucket[i].index, evn);
+ break;
+ case 0:
+ trMessageWall(rxBucket[i].index, evn);
+ break;
+ case 3:
+ {
+ int nSprite = rxBucket[i].index;
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->rxID > 0)
+ trMessageSprite(nSprite, evn);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+void evPost(int nIndex, int nType, unsigned int nDelta, COMMAND_ID command)
+{
+ dassert(command != kCommandCallback);
+ if (command == COMMAND_ID_2)
+ command = evGetSourceState(nType, nIndex) ? COMMAND_ID_1 : COMMAND_ID_0;
+ else if (command == COMMAND_ID_4)
+ command = evGetSourceState(nType, nIndex) ? COMMAND_ID_0 : COMMAND_ID_1;
+ EVENT evn;
+ evn.index = nIndex;
+ evn.type = nType;
+ evn.cmd = command;
+ // Inlined?
+ eventQ.PQueue->Insert(gFrameClock+nDelta, *(unsigned int*)&evn);
+}
+
+void evPost(int nIndex, int nType, unsigned int nDelta, CALLBACK_ID a4)
+{
+ EVENT evn;
+ evn.index = nIndex;
+ evn.type = nType;
+ evn.cmd = kCommandCallback;
+ evn.funcID = a4;
+ eventQ.PQueue->Insert(gFrameClock+nDelta, *(unsigned int*)&evn);
+}
+
+void evProcess(unsigned int nTime)
+{
+#if 0
+ while (1)
+ {
+ // Inlined?
+ char bDone;
+ if (eventQ.fNodeCount > 0 && nTime >= eventQ.queueItems[1])
+ bDone = 1;
+ else
+ bDone = 0;
+ if (!bDone)
+ break;
+#endif
+ while(eventQ.IsNotEmpty(nTime))
+ {
+ EVENT event = eventQ.ERemove();
+ if (event.cmd == kCommandCallback)
+ {
+ dassert(event.funcID < kCallbackMax);
+ dassert(gCallback[event.funcID] != NULL);
+ gCallback[event.funcID](event.index);
+ }
+ else
+ {
+ switch (event.type)
+ {
+ case 6:
+ trMessageSector(event.index, event);
+ break;
+ case 0:
+ trMessageWall(event.index, event);
+ break;
+ case 3:
+ trMessageSprite(event.index, event);
+ break;
+ }
+ }
+ }
+}
+
+void evKill(int a1, int a2)
+{
+ eventQ.Kill(a1, a2);
+}
+
+void evKill(int a1, int a2, CALLBACK_ID a3)
+{
+ eventQ.Kill(a1, a2, a3);
+}
+
+class EventQLoadSave : public LoadSave
+{
+public:
+ virtual void Load();
+ virtual void Save();
+};
+
+void EventQLoadSave::Load()
+{
+ if (eventQ.PQueue)
+ delete eventQ.PQueue;
+ Read(&eventQ, sizeof(eventQ));
+ if (VanillaMode())
+ eventQ.PQueue = new VanillaPriorityQueue();
+ else
+ eventQ.PQueue = new StdPriorityQueue();
+ int nEvents;
+ Read(&nEvents, sizeof(nEvents));
+ for (int i = 0; i < nEvents; i++)
+ {
+ EVENT event;
+ unsigned int eventtime;
+ Read(&eventtime, sizeof(eventtime));
+ Read(&event, sizeof(event));
+ eventQ.PQueue->Insert(eventtime, *(unsigned int*)&event);
+ }
+ Read(rxBucket, sizeof(rxBucket));
+ Read(bucketHead, sizeof(bucketHead));
+}
+
+void EventQLoadSave::Save()
+{
+ EVENT events[1024];
+ unsigned int eventstime[1024];
+ Write(&eventQ, sizeof(eventQ));
+ int nEvents = eventQ.PQueue->Size();
+ Write(&nEvents, sizeof(nEvents));
+ for (int i = 0; i < nEvents; i++)
+ {
+ eventstime[i] = eventQ.PQueue->LowestPriority();
+ events[i] = eventQ.ERemove();
+ Write(&eventstime[i], sizeof(eventstime[i]));
+ Write(&events[i], sizeof(events[i]));
+ }
+ dassert(eventQ.PQueue->Size() == 0);
+ for (int i = 0; i < nEvents; i++)
+ {
+ eventQ.PQueue->Insert(eventstime[i], *(unsigned int*)&events[i]);
+ }
+ Write(rxBucket, sizeof(rxBucket));
+ Write(bucketHead, sizeof(bucketHead));
+}
+
+static EventQLoadSave *myLoadSave;
+
+void EventQLoadSaveConstruct(void)
+{
+ myLoadSave = new EventQLoadSave();
+}
diff --git a/source/blood/src/eventq.h b/source/blood/src/eventq.h
new file mode 100644
index 000000000..e47f1592a
--- /dev/null
+++ b/source/blood/src/eventq.h
@@ -0,0 +1,68 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "callback.h"
+#define kMaxChannels 4096
+
+struct RXBUCKET
+{
+ unsigned int index : 13;
+ unsigned int type : 3;
+};
+extern RXBUCKET rxBucket[];
+extern unsigned short bucketHead[];
+
+enum COMMAND_ID {
+ COMMAND_ID_0 = 0,
+ COMMAND_ID_1,
+ COMMAND_ID_2,
+ COMMAND_ID_3,
+ COMMAND_ID_4,
+ COMMAND_ID_5,
+ COMMAND_ID_6,
+ COMMAND_ID_7,
+ COMMAND_ID_8,
+ COMMAND_ID_9,
+
+ kCommandCallback = 20,
+ COMMAND_ID_21,
+ kGDXCommandPaste = 53, // used by some new GDX types
+ kGDXCommandSpriteDamage, // used by sprite damager GDX type
+ COMMAND_ID_64 = 64,
+};
+
+struct EVENT {
+ unsigned int index : 13; // index
+ unsigned int type : 3; // type
+ unsigned int cmd : 8; // cmd
+ unsigned int funcID : 8; // callback
+}; // <= 4 bytes
+
+void evInit(void);
+char evGetSourceState(int nType, int nIndex);
+void evSend(int nIndex, int nType, int rxId, COMMAND_ID command);
+void evPost(int nIndex, int nType, unsigned int nDelta, COMMAND_ID command);
+void evPost(int nIndex, int nType, unsigned int nDelta, CALLBACK_ID a4);
+void evProcess(unsigned int nTime);
+void evKill(int a1, int a2);
+void evKill(int a1, int a2, CALLBACK_ID a3);
\ No newline at end of file
diff --git a/source/blood/src/fire.cpp b/source/blood/src/fire.cpp
new file mode 100644
index 000000000..a471a34f2
--- /dev/null
+++ b/source/blood/src/fire.cpp
@@ -0,0 +1,124 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include "build.h"
+#include "common_game.h"
+#include "blood.h"
+#include "fire.h"
+#include "globals.h"
+#include "misc.h"
+#include "tile.h"
+
+int fireSize = 128;
+int gDamping = 6;
+
+/*extern "C" */char CoolTable[1024];
+
+void CellularFrame(char *pFrame, int sizeX, int sizeY);
+
+char FrameBuffer[17280];
+char SeedBuffer[16][128];
+char *gCLU;
+
+void InitSeedBuffers(void)
+{
+ for (int i = 0; i < 16; i++)
+ for (int j = 0; j < fireSize; j += 2)
+ SeedBuffer[i][j] = SeedBuffer[i][j+1] = wrand();
+}
+
+void BuildCoolTable(void)
+{
+ for (int i = 0; i < 1024; i++)
+ CoolTable[i] = ClipLow((i-gDamping) / 4, 0);
+}
+
+void DoFireFrame(void)
+{
+ int nRand = qrand()&15;
+ for (int i = 0; i < 3; i++)
+ {
+ memcpy(FrameBuffer+16896+i*128, SeedBuffer[nRand], 128);
+ }
+ CellularFrame(FrameBuffer, 128, 132);
+ char *pData = tileLoadTile(2342);
+ char *pSource = FrameBuffer;
+ int x = fireSize;
+ do
+ {
+ int y = fireSize;
+ char *pDataBak = pData;
+ do
+ {
+ *pData = gCLU[*pSource];
+ pSource++;
+ pData += fireSize;
+ } while (--y);
+ pData = pDataBak + 1;
+ } while (--x);
+}
+
+void FireInit(void)
+{
+ memset(FrameBuffer, 0, sizeof(FrameBuffer));
+ BuildCoolTable();
+ InitSeedBuffers();
+ DICTNODE *pNode = gSysRes.Lookup("RFIRE", "CLU");
+ if (!pNode)
+ ThrowError("RFIRE.CLU not found");
+ gCLU = (char*)gSysRes.Lock(pNode);
+ for (int i = 0; i < 100; i++)
+ DoFireFrame();
+}
+
+void FireProcess(void)
+{
+ static int lastUpdate;
+ if (gGameClock < lastUpdate || lastUpdate + 2 < gGameClock)
+ {
+ DoFireFrame();
+ lastUpdate = gGameClock;
+ tileInvalidate(2342, -1, -1);
+ }
+}
+
+void CellularFrame(char *pFrame, int sizeX, int sizeY)
+{
+ int nSquare = sizeX * sizeY;
+ unsigned char *pPtr1 = (unsigned char*)pFrame;
+ while (nSquare--)
+ {
+ unsigned char *pPtr2 = pPtr1+sizeX;
+ int sum = *(pPtr2-1) + *pPtr2 + *(pPtr2+1) + *(pPtr2+sizeX);
+ if (*(pPtr2+sizeX) > 96)
+ {
+ pPtr2 += sizeX;
+ sum += *(pPtr2-1) + *pPtr2 + *(pPtr2+1) + *(pPtr2+sizeX);
+ sum >>= 1;
+ }
+ *pPtr1 = CoolTable[sum];
+ pPtr1++;
+ }
+}
diff --git a/source/blood/src/fire.h b/source/blood/src/fire.h
new file mode 100644
index 000000000..0fc76789b
--- /dev/null
+++ b/source/blood/src/fire.h
@@ -0,0 +1,26 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+void FireInit(void);
+void FireProcess(void);
\ No newline at end of file
diff --git a/source/blood/src/function.h b/source/blood/src/function.h
new file mode 100644
index 000000000..37a10d47b
--- /dev/null
+++ b/source/blood/src/function.h
@@ -0,0 +1,106 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+// function.h
+
+// file created by makehead.exe
+// these headers contain default key assignments, as well as
+// default button assignments and game function names
+// axis defaults are also included
+
+
+#ifndef function_public_h_
+#define function_public_h_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NUMGAMEFUNCTIONS 55
+#define MAXGAMEFUNCLEN 32
+
+extern char gamefunctions[NUMGAMEFUNCTIONS][MAXGAMEFUNCLEN];
+extern const char keydefaults[NUMGAMEFUNCTIONS*2][MAXGAMEFUNCLEN];
+extern const char oldkeydefaults[NUMGAMEFUNCTIONS*2][MAXGAMEFUNCLEN];
+
+enum GameFunction_t
+ {
+ gamefunc_Move_Forward,
+ gamefunc_Move_Backward,
+ gamefunc_Turn_Left,
+ gamefunc_Turn_Right,
+ gamefunc_Turn_Around,
+ gamefunc_Strafe,
+ gamefunc_Strafe_Left,
+ gamefunc_Strafe_Right,
+ gamefunc_Jump,
+ gamefunc_Crouch,
+ gamefunc_Run,
+ gamefunc_AutoRun,
+ gamefunc_Open,
+ gamefunc_Weapon_Fire,
+ gamefunc_Weapon_Special_Fire,
+ gamefunc_Aim_Up,
+ gamefunc_Aim_Down,
+ gamefunc_Aim_Center,
+ gamefunc_Look_Up,
+ gamefunc_Look_Down,
+ gamefunc_Tilt_Left,
+ gamefunc_Tilt_Right,
+ gamefunc_Weapon_1,
+ gamefunc_Weapon_2,
+ gamefunc_Weapon_3,
+ gamefunc_Weapon_4,
+ gamefunc_Weapon_5,
+ gamefunc_Weapon_6,
+ gamefunc_Weapon_7,
+ gamefunc_Weapon_8,
+ gamefunc_Weapon_9,
+ gamefunc_Weapon_10,
+ gamefunc_Inventory_Use,
+ gamefunc_Inventory_Left,
+ gamefunc_Inventory_Right,
+ gamefunc_Map_Toggle,
+ gamefunc_Map_Follow_Mode,
+ gamefunc_Shrink_Screen,
+ gamefunc_Enlarge_Screen,
+ gamefunc_Send_Message,
+ gamefunc_See_Coop_View,
+ gamefunc_See_Chase_View,
+ gamefunc_Mouse_Aiming,
+ gamefunc_Toggle_Crosshair,
+ gamefunc_Next_Weapon,
+ gamefunc_Previous_Weapon,
+ gamefunc_Holster_Weapon,
+ gamefunc_Show_Opponents_Weapon,
+ gamefunc_BeastVision,
+ gamefunc_CrystalBall,
+ gamefunc_JumpBoots,
+ gamefunc_MedKit,
+ gamefunc_ProximityBombs,
+ gamefunc_RemoteBombs,
+ gamefunc_Show_Console,
+ };
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/source/blood/src/fx.cpp b/source/blood/src/fx.cpp
new file mode 100644
index 000000000..30ee66b86
--- /dev/null
+++ b/source/blood/src/fx.cpp
@@ -0,0 +1,348 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include "build.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "blood.h"
+#include "callback.h"
+#include "config.h"
+#include "db.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gameutil.h"
+#include "levels.h"
+#include "seq.h"
+#include "trig.h"
+#include "view.h"
+
+CFX gFX;
+
+struct FXDATA {
+ CALLBACK_ID funcID; // callback
+ char at1; // detail
+ short at2; // seq
+ short at4; // flags
+ int at6; // gravity
+ int ata; // air drag
+ int ate;
+ short at12; // picnum
+ unsigned char at14; // xrepeat
+ unsigned char at15; // yrepeat
+ short at16; // cstat
+ signed char at18; // shade
+ char at19; // pal
+};
+
+FXDATA gFXData[] = {
+ { CALLBACK_ID_NONE, 0, 49, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 50, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 51, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 52, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 7, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 44, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 45, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 46, 1, -128, 8192, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 42, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 43, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 48, 3, -256, 8192, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 60, 3, -256, 8192, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_14, 2, 0, 1, 46603, 2048, 480, 2154, 40, 40, 0, -12, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 3, 46603, 5120, 480, 2269, 24, 24, 0, -128, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 3, 46603, 5120, 480, 1720, 24, 24, 0, -128, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 1, 58254, 3072, 480, 2280, 48, 48, 0, -128, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 1, 58254, 3072, 480, 3135, 48, 48, 0, -128, 0 },
+ { CALLBACK_ID_NONE, 0, 0, 3, 58254, 1024, 480, 3261, 32, 32, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 58254, 1024, 480, 3265, 32, 32, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 58254, 1024, 480, 3269, 32, 32, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 58254, 1024, 480, 3273, 32, 32, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 58254, 1024, 480, 3277, 32, 32, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 1, -27962, 8192, 600, 1128, 16, 16, 514, -16, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 1, -18641, 8192, 600, 1128, 12, 12, 514, -16, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 1, -9320, 8192, 600, 1128, 8, 8, 514, -16, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 1, -18641, 8192, 600, 1131, 32, 32, 514, -16, 0 },
+ { CALLBACK_ID_14, 2, 0, 3, 27962, 4096, 480, 733, 32, 32, 0, -16, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 18641, 4096, 120, 2261, 12, 12, 0, -128, 0 },
+ { CALLBACK_ID_NONE, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 58254, 3328, 480, 2185, 48, 48, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 0, 0, 3, 58254, 1024, 480, 2620, 48, 48, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 55, 1, -13981, 5120, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 56, 1, -13981, 5120, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 57, 1, 0, 2048, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 58, 1, 0, 2048, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 0, 0, 0, 960, 956, 32, 32, 610, 0, 0 },
+ { CALLBACK_ID_16, 2, 62, 0, 46603, 1024, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_16, 2, 63, 0, 46603, 1024, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_16, 2, 64, 0, 46603, 1024, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_16, 2, 65, 0, 46603, 1024, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_16, 2, 66, 0, 46603, 1024, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_16, 2, 67, 0, 46603, 1024, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 0, 0, 0, 838, 16, 16, 80, -8, 0 },
+ { CALLBACK_ID_NONE, 0, 0, 3, 34952, 8192, 0, 2078, 64, 64, 0, -8, 0 },
+ { CALLBACK_ID_NONE, 0, 0, 3, 34952, 8192, 0, 1106, 64, 64, 0, -8, 0 },
+ { CALLBACK_ID_NONE, 0, 0, 3, 58254, 3328, 480, 2406, 48, 48, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 3, 46603, 4096, 480, 3511, 64, 64, 0, -128, 0 },
+ { CALLBACK_ID_NONE, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 11, 3, -256, 8192, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 2, 11, 3, 0, 8192, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { CALLBACK_ID_NONE, 1, 30, 3, 0, 0, 0, 0, 40, 40, 80, -8, 0 },
+ { CALLBACK_ID_19, 2, 0, 3, 27962, 4096, 480, 4023, 32, 32, 0, -16, 0 },
+ { CALLBACK_ID_19, 2, 0, 3, 27962, 4096, 480, 4028, 32, 32, 0, -16, 0 },
+ { CALLBACK_ID_NONE, 2, 0, 0, 0, 0, 480, 926, 32, 32, 610, -12, 0 },
+ { CALLBACK_ID_NONE, 1, 70, 1, -13981, 5120, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+void CFX::sub_73FB0(int nSprite)
+{
+ if (nSprite < 0 || nSprite >= kMaxSprites)
+ return;
+ evKill(nSprite, 3);
+ if (sprite[nSprite].extra > 0)
+ seqKill(3, sprite[nSprite].extra);
+ DeleteSprite(nSprite);
+}
+
+void CFX::sub_73FFC(int nSprite)
+{
+ if (nSprite < 0 || nSprite >= kMaxSprites)
+ return;
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->extra > 0)
+ seqKill(3, pSprite->extra);
+ if (pSprite->statnum != kStatFree)
+ actPostSprite(nSprite, kStatFree);
+}
+
+spritetype * CFX::fxSpawn(FX_ID nFx, int nSector, int x, int y, int z, unsigned int a6)
+{
+ if (nSector < 0 || nSector >= numsectors)
+ return NULL;
+ int nSector2 = nSector;
+ if (!FindSector(x, y, z, &nSector2))
+ return NULL;
+ if (gbAdultContent && gGameOptions.nGameType <= 0)
+ {
+ switch (nFx)
+ {
+ case FX_0:
+ case FX_1:
+ case FX_2:
+ case FX_3:
+ case FX_13:
+ case FX_34:
+ case FX_35:
+ case FX_36:
+ return NULL;
+ default:
+ break;
+ }
+ }
+ if (nFx < 0 || nFx >= kFXMax)
+ return NULL;
+ FXDATA *pFX = &gFXData[nFx];
+ if (gStatCount[1] == 512)
+ {
+ int nSprite = headspritestat[1];;
+ while ((sprite[nSprite].hitag & 32) && nSprite != -1)
+ nSprite = nextspritestat[nSprite];
+ if (nSprite == -1)
+ return NULL;
+ sub_73FB0(nSprite);
+ }
+ spritetype *pSprite = actSpawnSprite(nSector, x, y, z, 1, 0);
+ pSprite->type = nFx;
+ pSprite->picnum = pFX->at12;
+ pSprite->cstat |= pFX->at16;
+ pSprite->shade = pFX->at18;
+ pSprite->pal = pFX->at19;
+ qsprite_filler[pSprite->index] = pFX->at1;
+ if (pFX->at14 > 0)
+ pSprite->xrepeat = pFX->at14;
+ if (pFX->at15 > 0)
+ pSprite->yrepeat = pFX->at15;
+ if ((pFX->at4 & 1) && Chance(0x8000))
+ pSprite->cstat |= 4;
+ if ((pFX->at4 & 2) && Chance(0x8000))
+ pSprite->cstat |= 8;
+ if (pFX->at2)
+ {
+ int nXSprite = dbInsertXSprite(pSprite->index);
+ seqSpawn(pFX->at2, 3, nXSprite, -1);
+ }
+ if (a6 == 0)
+ a6 = pFX->ate;
+ if (a6)
+ evPost((int)pSprite->index, 3, a6+Random2(a6>>1), CALLBACK_ID_1);
+ return pSprite;
+}
+
+void CFX::fxProcess(void)
+{
+ for (int nSprite = headspritestat[1]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ viewBackupSpriteLoc(nSprite, pSprite);
+ short nSector = pSprite->sectnum;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ dassert(pSprite->type < kFXMax);
+ FXDATA *pFXData = &gFXData[pSprite->type];
+ actAirDrag(pSprite, pFXData->ata);
+ if (xvel[nSprite])
+ pSprite->x += xvel[nSprite]>>12;
+ if (yvel[nSprite])
+ pSprite->y += yvel[nSprite]>>12;
+ if (zvel[nSprite])
+ pSprite->z += zvel[nSprite]>>8;
+ // Weird...
+ if (xvel[nSprite] || (yvel[nSprite] && pSprite->z >= sector[pSprite->sectnum].floorz))
+ {
+ updatesector(pSprite->x, pSprite->y, &nSector);
+ if (nSector == -1)
+ {
+ sub_73FFC(nSprite);
+ continue;
+ }
+ if (getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y) <= pSprite->z)
+ {
+ if (pFXData->funcID < 0 || pFXData->funcID >= kCallbackMax)
+ {
+ sub_73FFC(nSprite);
+ continue;
+ }
+ dassert(gCallback[pFXData->funcID] != NULL);
+ gCallback[pFXData->funcID](nSprite);
+ continue;
+ }
+ if (nSector != pSprite->sectnum)
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ ChangeSpriteSect(nSprite, nSector);
+ }
+ }
+ if (xvel[nSprite] || yvel[nSprite] || zvel[nSprite])
+ {
+ int32_t floorZ, ceilZ;
+ getzsofslope(nSector, pSprite->x, pSprite->y, &ceilZ, &floorZ);
+ if (ceilZ > pSprite->z && !(sector[nSector].ceilingstat&1))
+ {
+ sub_73FFC(nSprite);
+ continue;
+ }
+ if (floorZ < pSprite->z)
+ {
+ if (pFXData->funcID < 0 || pFXData->funcID >= kCallbackMax)
+ {
+ sub_73FFC(nSprite);
+ continue;
+ }
+ dassert(gCallback[pFXData->funcID] != NULL);
+ gCallback[pFXData->funcID](nSprite);
+ continue;
+ }
+ }
+ zvel[nSprite] += pFXData->at6;
+ }
+}
+
+void fxSpawnBlood(spritetype *pSprite, int a2)
+{
+ UNREFERENCED_PARAMETER(a2);
+ if (pSprite->sectnum < 0 || pSprite->sectnum >= numsectors)
+ return;
+ int nSector = pSprite->sectnum;
+ if (!FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector))
+ return;
+ if (gbAdultContent && gGameOptions.nGameType <= 0)
+ return;
+ spritetype *pBlood = gFX.fxSpawn(FX_27, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pBlood)
+ {
+ pBlood->ang = 1024;
+ xvel[pBlood->index] = Random2(0x6aaaa);
+ yvel[pBlood->index] = Random2(0x6aaaa);
+ zvel[pBlood->index] = -Random(0x10aaaa)-100;
+ evPost(pBlood->index, 3, 8, CALLBACK_ID_6);
+ }
+}
+
+void sub_746D4(spritetype *pSprite, int a2)
+{
+ UNREFERENCED_PARAMETER(a2);
+ if (pSprite->sectnum < 0 || pSprite->sectnum >= numsectors)
+ return;
+ int nSector = pSprite->sectnum;
+ if (!FindSector(pSprite->x, pSprite->y, pSprite->z, &nSector))
+ return;
+ if (gbAdultContent && gGameOptions.nGameType <= 0)
+ return;
+ spritetype *pSpawn;
+ if (pSprite->type == 221)
+ pSpawn = gFX.fxSpawn(FX_53, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ else
+ pSpawn = gFX.fxSpawn(FX_54, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ if (pSpawn)
+ {
+ pSpawn->ang = 1024;
+ xvel[pSpawn->index] = Random2(0x6aaaa);
+ yvel[pSpawn->index] = Random2(0x6aaaa);
+ zvel[pSpawn->index] = -Random(0x10aaaa)-100;
+ evPost(pSpawn->index, 3, 8, CALLBACK_ID_18);
+ }
+}
+
+void sub_74818(spritetype *pSprite, int z, int a3, int a4)
+{
+ int x = pSprite->x+mulscale28(pSprite->clipdist-4, Cos(pSprite->ang));
+ int y = pSprite->y+mulscale28(pSprite->clipdist-4, Sin(pSprite->ang));
+ x += mulscale30(a3, Cos(pSprite->ang+512));
+ y += mulscale30(a3, Sin(pSprite->ang+512));
+ spritetype *pShell = gFX.fxSpawn((FX_ID)(FX_37+Random(3)), pSprite->sectnum, x, y, z, 0);
+ if (pShell)
+ {
+ int nDist = (a4<<18)/120+Random2(((a4/4)<<18)/120);
+ int nAngle = pSprite->ang+Random2(56)+512;
+ xvel[pShell->index] = mulscale30(nDist, Cos(nAngle));
+ yvel[pShell->index] = mulscale30(nDist, Sin(nAngle));
+ zvel[pShell->index] = zvel[pSprite->index]-(0x20000+(Random2(40)<<18)/120);
+ }
+}
+
+void sub_74A18(spritetype *pSprite, int z, int a3, int a4)
+{
+ int x = pSprite->x+mulscale28(pSprite->clipdist-4, Cos(pSprite->ang));
+ int y = pSprite->y+mulscale28(pSprite->clipdist-4, Sin(pSprite->ang));
+ x += mulscale30(a3, Cos(pSprite->ang+512));
+ y += mulscale30(a3, Sin(pSprite->ang+512));
+ spritetype *pShell = gFX.fxSpawn((FX_ID)(FX_40+Random(3)), pSprite->sectnum, x, y, z, 0);
+ if (pShell)
+ {
+ int nDist = (a4<<18)/120+Random2(((a4/4)<<18)/120);
+ int nAngle = pSprite->ang+Random2(56)+512;
+ xvel[pShell->index] = mulscale30(nDist, Cos(nAngle));
+ yvel[pShell->index] = mulscale30(nDist, Sin(nAngle));
+ zvel[pShell->index] = zvel[pSprite->index]-(0x20000+(Random2(20)<<18)/120);
+ }
+}
diff --git a/source/blood/src/fx.h b/source/blood/src/fx.h
new file mode 100644
index 000000000..5d6c8809e
--- /dev/null
+++ b/source/blood/src/fx.h
@@ -0,0 +1,102 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common_game.h"
+
+enum FX_ID {
+ FX_NONE = -1,
+ FX_0 = 0,
+ FX_1,
+ FX_2,
+ FX_3,
+ FX_4,
+ FX_5,
+ FX_6,
+ FX_7,
+ FX_8,
+ FX_9,
+ FX_10,
+ FX_11,
+ FX_12,
+ FX_13,
+ FX_14,
+ FX_15,
+ FX_16,
+ FX_17,
+ FX_18,
+ FX_19,
+ FX_20,
+ FX_21,
+ FX_22,
+ FX_23,
+ FX_24,
+ FX_25,
+ FX_26,
+ FX_27,
+ FX_28,
+ FX_29,
+ FX_30,
+ FX_31,
+ FX_32,
+ FX_33,
+ FX_34,
+ FX_35,
+ FX_36,
+ FX_37,
+ FX_38,
+ FX_39,
+ FX_40,
+ FX_41,
+ FX_42,
+ FX_43,
+ FX_44,
+ FX_45,
+ FX_46,
+ FX_47,
+ FX_48,
+ FX_49,
+ FX_50,
+ FX_51,
+ FX_52,
+ FX_53,
+ FX_54,
+ FX_55,
+ FX_56,
+ kFXMax
+};
+
+class CFX {
+public:
+ void sub_73FB0(int);
+ void sub_73FFC(int);
+ spritetype * fxSpawn(FX_ID, int, int, int, int, unsigned int);
+ void fxProcess(void);
+};
+
+void fxSpawnBlood(spritetype *pSprite, int a2);
+void sub_746D4(spritetype *pSprite, int a2);
+void sub_74818(spritetype *pSprite, int z, int a3, int a4);
+void sub_74A18(spritetype *pSprite, int z, int a3, int a4);
+
+extern CFX gFX;
diff --git a/source/blood/src/gamedefs.h b/source/blood/src/gamedefs.h
new file mode 100644
index 000000000..440531f17
--- /dev/null
+++ b/source/blood/src/gamedefs.h
@@ -0,0 +1,72 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+//****************************************************************************
+//
+// gamedefs.h
+//
+// common defines between the game and the setup program
+//
+//****************************************************************************
+
+#ifndef gamedefs_public_h_
+#define gamedefs_public_h_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// config file name
+#define SETUPFILENAME APPBASENAME ".cfg"
+
+// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
+#define MAXJOYBUTTONS 32
+#define MAXJOYBUTTONSANDHATS (MAXJOYBUTTONS+4)
+
+// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
+#define MAXMOUSEAXES 2
+#define MAXMOUSEDIGITAL (MAXMOUSEAXES*2)
+
+// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp
+#define MAXJOYAXES 9
+#define MAXJOYDIGITAL (MAXJOYAXES*2)
+
+// default mouse scale
+#define DEFAULTMOUSEANALOGUESCALE 65536
+
+// default joystick settings
+
+#if defined(GEKKO)
+#define DEFAULTJOYSTICKANALOGUESCALE 16384
+#define DEFAULTJOYSTICKANALOGUEDEAD 1000
+#define DEFAULTJOYSTICKANALOGUESATURATE 9500
+#else
+#define DEFAULTJOYSTICKANALOGUESCALE 65536
+#define DEFAULTJOYSTICKANALOGUEDEAD 1000
+#define DEFAULTJOYSTICKANALOGUESATURATE 9500
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+
diff --git a/source/blood/src/gamemenu.cpp b/source/blood/src/gamemenu.cpp
new file mode 100644
index 000000000..e66c371d0
--- /dev/null
+++ b/source/blood/src/gamemenu.cpp
@@ -0,0 +1,3003 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#include "mouse.h"
+#include "common_game.h"
+#include "blood.h"
+#include "config.h"
+#include "gamemenu.h"
+#include "globals.h"
+#include "inifile.h"
+#include "levels.h"
+#include "menu.h"
+#include "qav.h"
+#include "resource.h"
+#include "view.h"
+
+CMenuTextMgr gMenuTextMgr;
+CGameMenuMgr gGameMenuMgr;
+
+extern CGameMenuItemPicCycle itemSorryPicCycle;
+extern CGameMenuItemQAV itemBloodQAV;
+
+CMenuTextMgr::CMenuTextMgr()
+{
+ at0 = -1;
+}
+
+static char buffer[21][45];
+
+void CMenuTextMgr::DrawText(const char *pString, int nFont, int x, int y, int nShade, int nPalette, bool shadow )
+{
+ viewDrawText(nFont, pString, x, y, nShade, nPalette, 0, shadow);
+}
+
+void CMenuTextMgr::GetFontInfo(int nFont, const char *pString, int *pXSize, int *pYSize)
+{
+ if (nFont < 0 || nFont >= 5)
+ return;
+ viewGetFontInfo(nFont, pString, pXSize, pYSize);
+}
+
+bool CGameMenuMgr::m_bInitialized = false;
+bool CGameMenuMgr::m_bActive = false;
+bool CGameMenuMgr::m_bFirstPush = true;
+
+CGameMenuMgr::CGameMenuMgr()
+{
+ dassert(!m_bInitialized);
+ m_bInitialized = true;
+ Clear();
+}
+
+CGameMenuMgr::~CGameMenuMgr()
+{
+ m_bInitialized = false;
+ Clear();
+}
+
+void CGameMenuMgr::InitializeMenu(void)
+{
+ if (pActiveMenu)
+ {
+ CGameMenuEvent event;
+ event.at0 = kMenuEventInit;
+ event.at2 = 0;
+ pActiveMenu->Event(event);
+ }
+}
+
+void CGameMenuMgr::DeInitializeMenu(void)
+{
+ if (pActiveMenu)
+ {
+ CGameMenuEvent event;
+ event.at0 = kMenuEventDeInit;
+ event.at2 = 0;
+ pActiveMenu->Event(event);
+ }
+}
+
+bool CGameMenuMgr::Push(CGameMenu *pMenu, int nItem)
+{
+ if (nMenuPointer == 0)
+ {
+ mouseReadAbs(&m_prevmousepos, &g_mouseAbs);
+ m_mouselastactivity = -M_MOUSETIMEOUT;
+ m_mousewake_watchpoint = 0;
+ mouseLockToWindow(0);
+ if (m_bFirstPush)
+ m_bFirstPush = false;
+ else
+ mouseMoveToCenter();
+ }
+ dassert(pMenu != NULL);
+ if (nMenuPointer == 8)
+ return false;
+ pActiveMenu = pMenuStack[nMenuPointer] = pMenu;
+ nMenuPointer++;
+ if (nItem >= 0)
+ pMenu->SetFocusItem(nItem);
+ m_bActive = true;
+ gInputMode = INPUT_MODE_1;
+ InitializeMenu();
+ m_menuchange_watchpoint = 1;
+ m_mousecaught = 1;
+ return true;
+}
+
+void CGameMenuMgr::Pop(void)
+{
+ if (nMenuPointer > 0)
+ {
+ DeInitializeMenu();
+ nMenuPointer--;
+ if (nMenuPointer == 0)
+ Deactivate();
+ else
+ pActiveMenu = pMenuStack[nMenuPointer-1];
+
+ m_menuchange_watchpoint = 1;
+ }
+ m_mousecaught = 1;
+}
+
+void CGameMenuMgr::PostPop(void)
+{
+ m_postPop = true;
+}
+
+void CGameMenuMgr::Draw(void)
+{
+ if (pActiveMenu)
+ {
+ pActiveMenu->Draw();
+ viewUpdatePages();
+ }
+
+ if (m_postPop)
+ {
+ Pop();
+ m_postPop = false;
+ }
+
+ int32_t mousestatus = mouseReadAbs(&m_mousepos, &g_mouseAbs);
+ if (mousestatus && g_mouseClickState == MOUSE_PRESSED)
+ m_mousedownpos = m_mousepos;
+
+ int16_t mousetile = 1043; // red arrow
+ if (tilesiz[mousetile].x > 0 && mousestatus)
+ {
+ if (!MOUSEACTIVECONDITION)
+ m_mousewake_watchpoint = 1;
+
+ if (MOUSEACTIVECONDITIONAL(mouseAdvanceClickState()) || m_mousepos.x != m_prevmousepos.x || m_mousepos.y != m_prevmousepos.y)
+ {
+ m_prevmousepos = m_mousepos;
+ m_mouselastactivity = totalclock;
+ }
+ else
+ m_mousewake_watchpoint = 0;
+
+ m_mousecaught = 0;
+ }
+ else
+ {
+ m_mouselastactivity = -M_MOUSETIMEOUT;
+
+ m_mousewake_watchpoint = 0;
+ }
+
+ // Display the mouse cursor, except on touch devices.
+ if (MOUSEACTIVECONDITION && !m_bFirstPush)
+ {
+ vec2_t cursorpos = { m_mousepos.x + (7 << 16), m_mousepos.y + (6 << 16) };
+
+ if ((unsigned) mousetile < MAXTILES)
+ {
+ int32_t scale = 65536;
+ int16_t rotate = 768;
+ uint32_t stat = 2|4|8;
+ int8_t alpha = MOUSEALPHA; //CURSORALPHA;
+ rotatesprite_fs_alpha(cursorpos.x, cursorpos.y, scale, rotate, mousetile, 0, 0, stat, alpha);
+ }
+ }
+ else
+ g_mouseClickState = MOUSE_IDLE;
+}
+
+void CGameMenuMgr::Clear(void)
+{
+ pActiveMenu = NULL;
+ memset(pMenuStack, 0, sizeof(pMenuStack));
+ nMenuPointer = 0;
+ m_postPop = false;
+}
+
+void CGameMenuMgr::Process(void)
+{
+ if (!pActiveMenu)
+ return;
+
+ if (m_menuchange_watchpoint > 0)
+ m_menuchange_watchpoint++;
+
+ CGameMenuEvent event;
+ event.at0 = 0;
+ event.at2 = 0;
+ char key;
+ if (!pActiveMenu->MouseEvent(event) && (key = keyGetScan()) != 0 )
+ {
+ keyFlushScans();
+ keyFlushChars();
+ event.at2 = key;
+ switch (key)
+ {
+ case sc_Escape:
+ event.at0 = kMenuEventEscape;
+ break;
+ case sc_Tab:
+ if (keystatus[sc_LeftShift] || keystatus[sc_RightShift])
+ event.at0 = kMenuEventUp;
+ else
+ event.at0 = kMenuEventDown;
+ break;
+ case sc_UpArrow:
+ case sc_kpad_8:
+ event.at0 = kMenuEventUp;
+ gGameMenuMgr.m_mouselastactivity = -M_MOUSETIMEOUT;
+ break;
+ case sc_DownArrow:
+ case sc_kpad_2:
+ event.at0 = kMenuEventDown;
+ gGameMenuMgr.m_mouselastactivity = -M_MOUSETIMEOUT;
+ break;
+ case sc_Enter:
+ case sc_kpad_Enter:
+ event.at0 = kMenuEventEnter;
+ break;
+ case sc_Space:
+ event.at0 = kMenuEventSpace;
+ break;
+ case sc_LeftArrow:
+ case sc_kpad_4:
+ event.at0 = kMenuEventLeft;
+ break;
+ case sc_RightArrow:
+ case sc_kpad_6:
+ event.at0 = kMenuEventRight;
+ break;
+ case sc_Delete:
+ case sc_kpad_Period:
+ event.at0 = kMenuEventDelete;
+ break;
+ case sc_BackSpace:
+ event.at0 = kMenuEventBackSpace;
+ break;
+ default:
+ event.at0 = kMenuEventKey;
+ break;
+ }
+ }
+ if (pActiveMenu->Event(event))
+ Pop();
+
+ if (m_menuchange_watchpoint >= 3)
+ m_menuchange_watchpoint = 0;
+}
+
+void CGameMenuMgr::Deactivate(void)
+{
+ Clear();
+ keyFlushScans();
+ keyFlushChars();
+ m_bActive = false;
+
+ mouseLockToWindow(1);
+ gInputMode = INPUT_MODE_0;
+}
+
+bool CGameMenuMgr::MouseOutsideBounds(vec2_t const * const pos, const int32_t x, const int32_t y, const int32_t width, const int32_t height)
+{
+ return pos->x < x || pos->x >= x + width || pos->y < y || pos->y >= y + height;
+}
+
+CGameMenu::CGameMenu()
+{
+ m_nItems = 0;
+ m_nFocus = at8 = -1;
+ atc = 0;
+}
+
+CGameMenu::CGameMenu(int unk)
+{
+ m_nItems = 0;
+ m_nFocus = at8 = -1;
+ atc = unk;
+}
+
+CGameMenu::~CGameMenu()
+{
+ if (!atc)
+ return;
+ for (int i = 0; i < m_nItems; i++)
+ {
+ if (pItemList[i] != &itemBloodQAV && pItemList[i] != &itemSorryPicCycle)
+ delete pItemList[i];
+ }
+}
+
+void CGameMenu::InitializeItems(CGameMenuEvent &event)
+{
+ for (int i = 0; i < m_nItems; i++)
+ {
+ pItemList[i]->Event(event);
+ }
+}
+
+void CGameMenu::Draw(void)
+{
+ for (int i = 0; i < m_nItems; i++)
+ {
+ if (pItemList[i]->pPreDrawCallback)
+ pItemList[i]->pPreDrawCallback(pItemList[i]);
+ if (i == m_nFocus || (i != m_nFocus && !pItemList[i]->bNoDraw))
+ pItemList[i]->Draw();
+ }
+}
+
+bool CGameMenu::Event(CGameMenuEvent &event)
+{
+ if (m_nItems <= 0)
+ return true;
+ switch (event.at0)
+ {
+ case kMenuEventInit:
+ case kMenuEventDeInit:
+ if (at8 >= 0)
+ m_nFocus = at8;
+ InitializeItems(event);
+ return false;
+ }
+ if (m_nFocus < 0)
+ return true;
+ return pItemList[m_nFocus]->Event(event);
+}
+
+void CGameMenu::Add(CGameMenuItem *pItem, bool active)
+{
+ dassert(pItem != NULL);
+ dassert(m_nItems < kMaxGameMenuItems);
+ pItemList[m_nItems] = pItem;
+ pItem->pMenu = this;
+ if (active)
+ m_nFocus = at8 = m_nItems;
+ m_nItems++;
+}
+
+void CGameMenu::SetFocusItem(int nItem)
+{
+ dassert(nItem >= 0 && nItem < m_nItems && nItem < kMaxGameMenuItems);
+ if (CanSelectItem(nItem))
+ m_nFocus = at8 = nItem;
+}
+
+void CGameMenu::SetFocusItem(CGameMenuItem *pItem)
+{
+ for (int i = 0; i < m_nItems; i++)
+ if (pItemList[i] == pItem)
+ {
+ SetFocusItem(i);
+ break;
+ }
+}
+
+bool CGameMenu::CanSelectItem(int nItem)
+{
+ dassert(nItem >= 0 && nItem < m_nItems && nItem < kMaxGameMenuItems);
+ return pItemList[nItem]->bCanSelect && pItemList[nItem]->bEnable;
+}
+
+void CGameMenu::FocusPrevItem(void)
+{
+ dassert(m_nFocus >= -1 && m_nFocus < m_nItems && m_nFocus < kMaxGameMenuItems);
+ int t = m_nFocus;
+ do
+ {
+ m_nFocus--;
+ if (m_nFocus < 0)
+ m_nFocus += m_nItems;
+ if (CanSelectItem(m_nFocus))
+ break;
+ } while(t != m_nFocus);
+}
+
+void CGameMenu::FocusNextItem(void)
+{
+ dassert(m_nFocus >= -1 && m_nFocus < m_nItems && m_nFocus < kMaxGameMenuItems);
+ int t = m_nFocus;
+ do
+ {
+ m_nFocus++;
+ if (m_nFocus >= m_nItems)
+ m_nFocus = 0;
+ if (CanSelectItem(m_nFocus))
+ break;
+ } while(t != m_nFocus);
+}
+
+bool CGameMenu::IsFocusItem(CGameMenuItem *pItem)
+{
+ if (m_nFocus < 0)
+ return false;
+ dassert(m_nFocus >= 0 && m_nFocus < m_nItems && m_nFocus < kMaxGameMenuItems);
+ return pItemList[m_nFocus] == pItem;
+}
+
+bool CGameMenu::MouseEvent(CGameMenuEvent &event)
+{
+ if (m_nItems <= 0 || m_nFocus < 0)
+ return true;
+ return pItemList[m_nFocus]->MouseEvent(event);
+}
+
+CGameMenuItem::CGameMenuItem()
+{
+ m_pzText = NULL;
+ m_nX = m_nY = m_nWidth = 0;
+ bCanSelect = 1;
+ bEnable = 1;
+ m_nFont = -1;
+ pMenu = NULL;
+ bNoDraw = 0;
+ pPreDrawCallback = NULL;
+}
+
+CGameMenuItem::~CGameMenuItem()
+{
+}
+
+bool CGameMenuItem::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEscape:
+ return true;
+ case kMenuEventUp:
+ pMenu->FocusPrevItem();
+ break;
+ case kMenuEventDown:
+ pMenu->FocusNextItem();
+ break;
+ }
+ return false;
+}
+
+bool CGameMenuItem::MouseEvent(CGameMenuEvent &event)
+{
+ event.at0 = kMenuEventNone;
+ if (MOUSEINACTIVECONDITIONAL(MOUSE_GetButtons()&LEFT_MOUSE))
+ {
+ event.at0 = kMenuEventEnter;
+ MOUSE_ClearButton(LEFT_MOUSE);
+ }
+ else if (MOUSE_GetButtons()&RIGHT_MOUSE)
+ {
+ event.at0 = kMenuEventEscape;
+ MOUSE_ClearButton(RIGHT_MOUSE);
+ }
+#if 0
+ else if (MOUSEINACTIVECONDITIONAL((MOUSE_GetButtons()&LEFT_MOUSE) && (MOUSE_GetButtons()&WHEELUP_MOUSE)))
+ {
+ MOUSE_ClearButton(WHEELUP_MOUSE);
+ event.bAutoAim = kMenuEventScrollLeft;
+ }
+ else if (MOUSEINACTIVECONDITIONAL((MOUSE_GetButtons()&LEFT_MOUSE) && (MOUSE_GetButtons()&WHEELDOWN_MOUSE)))
+ {
+ MOUSE_ClearButton(WHEELDOWN_MOUSE);
+ event.bAutoAim = kMenuEventScrollRight;
+ }
+#endif
+ else if (MOUSE_GetButtons()&WHEELUP_MOUSE)
+ {
+ MOUSE_ClearButton(WHEELUP_MOUSE);
+ event.at0 = kMenuEventUp;
+ }
+ else if (MOUSE_GetButtons()&WHEELDOWN_MOUSE)
+ {
+ MOUSE_ClearButton(WHEELDOWN_MOUSE);
+ event.at0 = kMenuEventDown;
+ }
+ return event.at0 != kMenuEventNone;
+}
+
+CGameMenuItemText::CGameMenuItemText()
+{
+ m_pzText = 0;
+ bEnable = 0;
+}
+
+CGameMenuItemText::CGameMenuItemText(const char *a1, int a2, int a3, int a4, int a5)
+{
+ m_nWidth = 0;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ at20 = a5;
+ bEnable = 0;
+}
+
+void CGameMenuItemText::Draw(void)
+{
+ if (m_pzText)
+ {
+ int width;
+ int x = m_nX;
+ switch (at20)
+ {
+ case 1:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX-width/2;
+ break;
+ case 2:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX-width;
+ break;
+ }
+ gMenuTextMgr.DrawText(m_pzText,m_nFont, x, m_nY, -128, 0, false);
+ }
+}
+
+CGameMenuItemTitle::CGameMenuItemTitle()
+{
+ m_pzText = 0;
+ bEnable = 0;
+}
+
+CGameMenuItemTitle::CGameMenuItemTitle(const char *a1, int a2, int a3, int a4, int a5)
+{
+ m_nWidth = 0;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ at20 = a5;
+ bEnable = 0;
+}
+
+void CGameMenuItemTitle::Draw(void)
+{
+ if (m_pzText)
+ {
+ int height;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ if (at20 >= 0)
+ rotatesprite(320<<15, m_nY<<16, 65536, 0, at20, -128, 0, 78, 0, 0, xdim-1, ydim-1);
+ viewDrawText(m_nFont, m_pzText, m_nX, m_nY-height/2, -128, 0, 1, false);
+ }
+}
+
+CGameMenuItemZBool::CGameMenuItemZBool()
+{
+ at20 = false;
+ m_pzText = 0;
+ at21 = "On";
+ at25 = "Off";
+}
+
+CGameMenuItemZBool::CGameMenuItemZBool(const char *a1, int a2, int a3, int a4, int a5, bool a6, void(*a7)(CGameMenuItemZBool *), const char *a8, const char *a9)
+{
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ at20 = a6;
+ at29 = a7;
+ if (!a8)
+ at21 = "On";
+ else
+ at21 = a8;
+ if (!a9)
+ at25 = "Off";
+ else
+ at25 = a9;
+}
+
+void CGameMenuItemZBool::Draw(void)
+{
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ if (m_pzText)
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, m_nX, m_nY, shade, pal, false);
+ const char *value = at20 ? at21 : at25;
+ int width, height;
+ gMenuTextMgr.GetFontInfo(m_nFont, value, &width, &height);
+ gMenuTextMgr.DrawText(value, m_nFont, m_nWidth-1+m_nX-width, m_nY, shade, pal, false);
+ int mx = m_nX<<16;
+ int my = m_nY<<16;
+ int mw = m_nWidth<<16;
+ int mh = height<<16;
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, mx, my, mw, mh))
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+ }
+}
+
+bool CGameMenuItemZBool::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ case kMenuEventSpace:
+ at20 = !at20;
+ if (at29)
+ at29(this);
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemChain::CGameMenuItemChain()
+{
+ m_pzText = NULL;
+ at24 = NULL;
+ at28 = -1;
+ at2c = NULL;
+ at30 = 0;
+}
+
+CGameMenuItemChain::CGameMenuItemChain(const char *a1, int a2, int a3, int a4, int a5, int a6, CGameMenu *a7, int a8, void(*a9)(CGameMenuItemChain *), int a10)
+{
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ at20 = a6;
+ at24 = a7;
+ at28 = a8;
+ at2c = a9;
+ at30 = a10;
+}
+
+void CGameMenuItemChain::Draw(void)
+{
+ if (!m_pzText) return;
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int width, height;
+ int x = m_nX;
+ int y = m_nY;
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, &height);
+ switch (at20)
+ {
+ case 1:
+ x = m_nX+m_nWidth/2-width/2;
+ break;
+ case 2:
+ x = m_nX+m_nWidth-1-width;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, m_nY, shade, pal, true);
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, x<<16, y<<16, width<<16, height<<16)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, x<<16, y<<16, width<<16, height<<16)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, x<<16, y<<16, width<<16, height<<16))
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+ }
+}
+
+bool CGameMenuItemChain::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ if (at2c)
+ at2c(this);
+ if (at24)
+ gGameMenuMgr.Push(at24, at28);
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItem7EA1C::CGameMenuItem7EA1C()
+{
+ m_pzText = NULL;
+ at24 = NULL;
+ at28 = -1;
+ at2c = NULL;
+ at30 = 0;
+ at34 = NULL;
+ at38[0] = 0;
+ at48[0] = 0;
+}
+
+CGameMenuItem7EA1C::CGameMenuItem7EA1C(const char *a1, int a2, int a3, int a4, int a5, const char *a6, const char *a7, int a8, int a9, void(*a10)(CGameMenuItem7EA1C *), int a11)
+{
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ at20 = a8;
+ at28 = a9;
+ at2c = a10;
+ at30 = a11;
+ strncpy(at38, a6, 15);
+ strncpy(at48, a7, 15);
+}
+
+void CGameMenuItem7EA1C::Draw(void)
+{
+ if (!m_pzText) return;
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int width;
+ int x = m_nX;
+ switch (at20)
+ {
+ case 1:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX+m_nWidth/2-width/2;
+ break;
+ case 2:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX+m_nWidth-1-width;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, m_nY, shade, pal, true);
+}
+
+void CGameMenuItem7EA1C::Setup(void)
+{
+ if (!at34 || !at24)
+ return;
+ if (!at34->SectionExists(at48))
+ return;
+ const char *title = at34->GetKeyString(at48, "Title", at48);
+ at24->Add(new CGameMenuItemTitle(title, 1, 160, 20, 2038), false);
+ at24->Add(&itemSorryPicCycle, true);
+ int y = 40;
+ for (int i = 0; i < 21; i++)
+ {
+ sprintf(buffer[i], "Line%d", i+1);
+ if (!at34->KeyExists(at48, buffer[i]))
+ break;
+ const char *line = at34->GetKeyString(at48, buffer[i], NULL);
+ if (line)
+ {
+ if (*line == 0)
+ {
+ y += 10;
+ continue;
+ }
+ at24->Add(new CGameMenuItemText(line, 1, 160, y, 1), false);
+ y += 20;
+ }
+ }
+ at24->Add(&itemBloodQAV, false);
+}
+
+bool CGameMenuItem7EA1C::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ {
+ if (at2c)
+ at2c(this);
+ if (at24)
+ delete at24;
+ at24 = new CGameMenu(1);
+ DICTNODE *pRes = gGuiRes.Lookup(at38, "MNU");
+ if (pRes)
+ {
+ at34 = new IniFile(gGuiRes.Load(pRes));
+ Setup();
+ }
+ if (at24)
+ gGameMenuMgr.Push(at24, at28);
+ return false;
+ }
+ case kMenuEventDeInit:
+ if (at34)
+ {
+ delete at34;
+ at34 = NULL;
+ }
+ if (at24)
+ {
+ delete at24;
+ at24 = NULL;
+ }
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItem7EE34::CGameMenuItem7EE34()
+{
+ m_pzText = NULL;
+ at28 = NULL;
+ at20 = -1;
+ at2c = NULL;
+}
+
+CGameMenuItem7EE34::CGameMenuItem7EE34(const char *a1, int a2, int a3, int a4, int a5, int a6)
+{
+ m_pzText = NULL;
+ at28 = NULL;
+ at20 = -1;
+ at2c = NULL;
+ m_nFont = a2;
+ m_nX = a3;
+ m_pzText = a1;
+ m_nY = a4;
+ m_nWidth = a5;
+ at24 = a6;
+}
+
+void CGameMenuItem7EE34::Draw(void)
+{
+ if (!m_pzText) return;
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int width;
+ int x = m_nX;
+ switch (at24)
+ {
+ case 1:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX+m_nWidth/2-width/2;
+ break;
+ case 2:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX+m_nWidth-1-width;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, m_nY, shade, pal, true);
+}
+
+extern void SetVideoModeOld(CGameMenuItemChain *pItem);
+
+void CGameMenuItem7EE34::Setup(void)
+{
+ if (!at28)
+ return;
+ at28->Add(new CGameMenuItemTitle("Video Mode", 1, 160, 20, 2038), false);
+ if (!at2c)
+ {
+ at2c = new CGameMenu(1);
+ at2c->Add(new CGameMenuItemTitle(" Mode Change ", 1, 160, 20, 2038), false);
+ at2c->Add(&itemSorryPicCycle, true);
+ CGameMenuItem *pItem1 = new CGameMenuItemText("VIDEO MODE WAS SET", 1, 160, 90, 1);
+ CGameMenuItem *pItem2 = new CGameMenuItemText("NOT ALL MODES Work correctly", 1, 160, 110, 1);
+ CGameMenuItem *pItem3 = new CGameMenuItemText("Press ESC to exit", 3, 160, 140, 1);
+ at2c->Add(pItem1, false);
+ pItem1->bEnable = 0;
+ at2c->Add(pItem2, false);
+ pItem2->bEnable = 0;
+ at2c->Add(pItem3, true);
+ pItem3->bEnable = 1;
+ at2c->Add(&itemBloodQAV, false);
+ }
+ sprintf(buffer[0], "640 x 480 (default)");
+ int y = 40;
+ at28->Add(new CGameMenuItemChain(buffer[0], 3, 0, y, 320, 1, at2c, -1, SetVideoModeOld, validmodecnt), true);
+ y += 20;
+ for (int i = 0; i < validmodecnt && i < 20; i++)
+ {
+ sprintf(buffer[i+1], "%d x %d", validmode[i].xdim, validmode[i].ydim);
+ at28->Add(new CGameMenuItemChain(buffer[i+1], 3, 0, y, 320, 1, at2c, -1, SetVideoModeOld, i), false);
+ if (validmodecnt > 10)
+ y += 7;
+ else
+ y += 15;
+ }
+ at28->Add(&itemBloodQAV, false);
+}
+
+bool CGameMenuItem7EE34::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ if (at28)
+ delete at28;
+ at28 = new CGameMenu(1);
+ Setup();
+ if (at28)
+ gGameMenuMgr.Push(at28, at20);
+ return false;
+ case kMenuEventDeInit:
+ if (at28)
+ {
+ delete at28;
+ at28 = 0;
+ }
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemChain7F2F0::CGameMenuItemChain7F2F0()
+{
+ at34 = -1;
+}
+
+CGameMenuItemChain7F2F0::CGameMenuItemChain7F2F0(char *a1, int a2, int a3, int a4, int a5, int a6, CGameMenu *a7, int a8, void(*a9)(CGameMenuItemChain *), int a10) :
+ CGameMenuItemChain(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+{
+ at34 = a10;
+}
+
+bool CGameMenuItemChain7F2F0::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ if (at34 > -1)
+ gGameOptions.nEpisode = at34;
+ return CGameMenuItemChain::Event(event);
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemBitmap::CGameMenuItemBitmap()
+{
+ m_pzText = NULL;
+}
+
+CGameMenuItemBitmap::CGameMenuItemBitmap(const char *a1, int a2, int a3, int a4, int a5)
+{
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ at20 = a5;
+}
+
+void CGameMenuItemBitmap::Draw(void)
+{
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (bEnable && pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int x = m_nX;
+ int y = m_nY;
+ if (m_pzText)
+ {
+ int height;
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, y, shade, pal, false);
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ y += height + 2;
+ }
+ rotatesprite(x<<15,y<<15, 65536, 0, at20, 0, 0, 82, 0, 0, xdim-1,ydim-1);
+}
+
+bool CGameMenuItemBitmap::Event(CGameMenuEvent &event)
+{
+ if (bEnable && pMenu->IsFocusItem(this))
+ pMenu->FocusNextItem();
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemBitmapLS::CGameMenuItemBitmapLS()
+{
+ m_pzText = NULL;
+}
+
+CGameMenuItemBitmapLS::CGameMenuItemBitmapLS(const char *a1, int a2, int a3, int a4, int a5)
+{
+ at24 = -1;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ at28 = a5;
+}
+
+void CGameMenuItemBitmapLS::Draw(void)
+{
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (bEnable && pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int x = m_nX;
+ int y = m_nY;
+ if (m_pzText)
+ {
+ int height;
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, y, shade, pal, false);
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ y += height + 2;
+ }
+ char stat;
+ int16_t ang;
+ int picnum;
+ if (at24 == -1)
+ {
+ stat = 66;
+ ang = 0;
+ picnum = at28;
+ }
+ else
+ {
+ ang = 512;
+ stat = 70;
+ picnum = at24;
+ }
+ rotatesprite(200<<15,215<<15,32768, ang, picnum, 0, 0, stat, 0, 0, xdim-1, ydim-1);
+}
+
+bool CGameMenuItemBitmapLS::Event(CGameMenuEvent &event)
+{
+ if (bEnable && pMenu->IsFocusItem(this))
+ pMenu->FocusNextItem();
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemKeyList::CGameMenuItemKeyList()
+{
+ m_pzText = NULL;
+ m_nFont = 3;
+ m_nX = 0;
+ m_nY = 0;
+ nRows = 0;
+ nTopDelta = 0;
+ nFocus = 0;
+ nGameFuncs = 0;
+ bScan = false;
+}
+
+CGameMenuItemKeyList::CGameMenuItemKeyList(const char *a1, int a2, int a3, int a4, int a5, int a6, int a7, void(*a8)(CGameMenuItemKeyList *))
+{
+ nTopDelta = 0;
+ nFocus = 0;
+ bScan = false;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ nRows = a6;
+ pCallback = a8;
+ nGameFuncs = a7;
+}
+
+void CGameMenuItemKeyList::Scan(void)
+{
+ KB_FlushKeyboardQueue();
+ KB_FlushKeyboardQueueScans();
+ KB_ClearKeysDown();
+ KB_LastScan = 0;
+ bScan = true;
+}
+
+extern uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2];
+void CGameMenuItemKeyList::Draw(void)
+{
+ char buffer[40], buffer2[40];
+ int width, height;
+ int shade;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ int y = m_nY;
+ int k = nFocus - nTopDelta;
+ int nNewFocus = nFocus;
+ bool bClick = false;
+ for (int i = 0; i < nRows; i++, y += height, k++)
+ {
+ char key1, key2;
+ key1 = KeyboardKeys[k][0];
+ key2 = KeyboardKeys[k][1];
+ const char *sKey1 = key1 == sc_Tilde ? "Tilde" : KB_ScanCodeToString(key1);
+ const char *sKey2 = key2 == sc_Tilde ? "Tilde" : KB_ScanCodeToString(key2);
+ sprintf(buffer, "%s", CONFIG_FunctionNumToName(k));
+ if (key2 == 0 || key2 == 0xff)
+ {
+ if (key1 == 0 || key1 == 0xff)
+ sprintf(buffer2, "????");
+ else
+ sprintf(buffer2, "%s", sKey1);
+ }
+ else
+ sprintf(buffer2, "%s or %s", sKey1, sKey2);
+
+ if (k == nFocus)
+ {
+ shade = 32;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ viewDrawText(3, buffer, m_nX, y, shade, 0, 0, false);
+ const char *sVal;
+ if (bScan && (gGameClock & 32))
+ sVal = "____";
+ else
+ sVal = buffer2;
+ gMenuTextMgr.GetFontInfo(m_nFont, sVal, &width, 0);
+ viewDrawText(m_nFont, sVal, m_nX+m_nWidth-1-width, y, shade, 0, 0, false);
+ }
+ else
+ {
+ viewDrawText(3, buffer, m_nX, y, 24, 0, 0, false);
+ gMenuTextMgr.GetFontInfo(m_nFont, buffer2, &width, 0);
+ viewDrawText(m_nFont, buffer2, m_nX+m_nWidth-1-width, y, 24, 0, 0, false);
+ }
+ int mx = m_nX<<16;
+ int my = y<<16;
+ int mw = m_nWidth<<16;
+ int mh = height<<16;
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ nNewFocus = k;
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, mx, my, mw, mh))
+ {
+ nNewFocus = k;
+ bClick = true;
+ }
+ }
+ }
+ nTopDelta += nNewFocus-nFocus;
+ nFocus = nNewFocus;
+ if (bClick)
+ {
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+}
+
+bool CGameMenuItemKeyList::Event(CGameMenuEvent &event)
+{
+ if (bScan)
+ {
+ if (KB_LastScan && KB_LastScan != sc_Pause)
+ {
+ if (KB_KeyWaiting())
+ KB_GetCh();
+ char key1, key2;
+ extern uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2];
+ key1 = KeyboardKeys[nFocus][0];
+ key2 = KeyboardKeys[nFocus][1];
+ if (key1 > 0 && key2 != KB_LastScan)
+ key2 = key1;
+ key1 = KB_LastScan;
+ if (key1 == key2)
+ key2 = 0;
+ uint8_t oldKey[2];
+ oldKey[0] = KeyboardKeys[nFocus][0];
+ oldKey[1] = KeyboardKeys[nFocus][1];
+ KeyboardKeys[nFocus][0] = key1;
+ KeyboardKeys[nFocus][1] = key2;
+ CONFIG_MapKey(nFocus, key1, oldKey[0], key2, oldKey[1]);
+ KB_FlushKeyboardQueue();
+ KB_FlushKeyboardQueueScans();
+ KB_ClearKeysDown();
+ keyFlushScans();
+ keyFlushChars();
+ bScan = 0;
+ }
+ return false;
+ }
+ switch (event.at0)
+ {
+ case kMenuEventUp:
+ if (event.at2 == sc_Tab || nFocus == 0)
+ {
+ pMenu->FocusPrevItem();
+ return false;
+ }
+ nFocus--;
+ if (nTopDelta > 0)
+ nTopDelta--;
+ return false;
+ case kMenuEventDown:
+ if (event.at2 == sc_Tab || nFocus == nGameFuncs-1)
+ {
+ pMenu->FocusNextItem();
+ return false;
+ }
+ nFocus++;
+ if (nTopDelta+1 < nRows)
+ nTopDelta++;
+ return false;
+ case kMenuEventEnter:
+ if (pCallback)
+ pCallback(this);
+ Scan();
+ return false;
+ case kMenuEventDelete:
+ if (keystatus[sc_LeftControl] || keystatus[sc_RightControl])
+ {
+ uint8_t oldKey[2];
+ oldKey[0] = KeyboardKeys[nFocus][0];
+ oldKey[1] = KeyboardKeys[nFocus][1];
+ KeyboardKeys[nFocus][0] = 0;
+ KeyboardKeys[nFocus][1] = 0;
+ CONFIG_MapKey(nFocus, 0, oldKey[0], 0, oldKey[1]);
+ }
+ return false;
+ case kMenuEventScrollUp:
+ if (nFocus-nTopDelta > 0)
+ {
+ nTopDelta++;
+ if (nTopDelta>0)
+ {
+ nFocus--;
+ nTopDelta--;
+ }
+ }
+ return false;
+ case kMenuEventScrollDown:
+ if (nFocus-nTopDelta+nRows < nGameFuncs)
+ {
+ nTopDelta--;
+ if (nTopDelta+1 < nRows)
+ {
+ nFocus++;
+ nTopDelta++;
+ }
+ }
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+bool CGameMenuItemKeyList::MouseEvent(CGameMenuEvent &event)
+{
+ event.at0 = kMenuEventNone;
+ if (MOUSEACTIVECONDITIONAL(MOUSE_GetButtons()&WHEELUP_MOUSE))
+ {
+ gGameMenuMgr.m_mouselastactivity = totalclock;
+ MOUSE_ClearButton(WHEELUP_MOUSE);
+ event.at0 = kMenuEventScrollUp;
+ }
+ else if (MOUSEACTIVECONDITIONAL(MOUSE_GetButtons()&WHEELDOWN_MOUSE))
+ {
+ gGameMenuMgr.m_mouselastactivity = totalclock;
+ MOUSE_ClearButton(WHEELDOWN_MOUSE);
+ event.at0 = kMenuEventScrollDown;
+ }
+ else
+ return CGameMenuItem::MouseEvent(event);
+ return event.at0 != kMenuEventNone;
+}
+
+CGameMenuItemSlider::CGameMenuItemSlider()
+{
+ m_pzText = NULL;
+ m_nFont = -1;
+ m_nX = 0;
+ m_nY = 0;
+ nValue = 0;
+ nRangeLow = 0;
+ nStep = 0;
+ pCallback = NULL;
+ pValue = NULL;
+ nSliderTile = 2204;
+ nCursorTile = 2028;
+ nShowValue = kMenuSliderNone;
+}
+
+CGameMenuItemSlider::CGameMenuItemSlider(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, int _nValue, int _nRangeLow, int _nRangeHigh, int _nStep, void(*_pCallback)(CGameMenuItemSlider *), int _nSliderTile, int _nCursorTile, int _nShowValue)
+{
+ m_pzText = _pzText;
+ m_nFont = _nFont;
+ m_nX = _nX;
+ m_nY = _nY;
+ m_nWidth = _nWidth;
+ nRangeLow = _nRangeLow;
+ nRangeHigh = _nRangeHigh;
+ nStep = _nStep;
+ nValue = ClipRange(_nValue, nRangeLow, nRangeHigh);
+ pCallback = _pCallback;
+ nSliderTile = 2204;
+ nCursorTile = 2028;
+ if (_nSliderTile >= 0)
+ nSliderTile = _nSliderTile;
+ if (_nCursorTile >= 0)
+ nCursorTile = _nCursorTile;
+ nShowValue = _nShowValue;
+}
+
+CGameMenuItemSlider::CGameMenuItemSlider(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, int *pnValue, int _nRangeLow, int _nRangeHigh, int _nStep, void(*_pCallback)(CGameMenuItemSlider *), int _nSliderTile, int _nCursorTile, int _nShowValue)
+{
+ m_pzText = _pzText;
+ m_nFont = _nFont;
+ m_nX = _nX;
+ m_nY = _nY;
+ m_nWidth = _nWidth;
+ nRangeLow = _nRangeLow;
+ nRangeHigh = _nRangeHigh;
+ nStep = _nStep;
+ dassert(pnValue != NULL);
+ pValue = pnValue;
+ nValue = ClipRange(*pnValue, nRangeLow, nRangeHigh);
+ pCallback = _pCallback;
+ nSliderTile = 2204;
+ nCursorTile = 2028;
+ if (_nSliderTile >= 0)
+ nSliderTile = _nSliderTile;
+ if (_nCursorTile >= 0)
+ nCursorTile = _nCursorTile;
+ nShowValue = _nShowValue;
+}
+
+void CGameMenuItemSlider::Draw(void)
+{
+ char buffer[16];
+ int height;
+ nValue = pValue ? *pValue : nValue;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ int shade = bEnable ? 32 : 48;
+ int shade2 = bEnable ? 0 : 16;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ if (m_pzText)
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, m_nX, m_nY, shade, pal, false);
+ int sliderX = m_nX+m_nWidth-1-tilesiz[nSliderTile].x/2;
+ rotatesprite(sliderX<<16, (m_nY+height/2)<<16, 65536, 0, nSliderTile, shade2, pal, 10, 0, 0, xdim-1, ydim-1);
+ int nRange = nRangeHigh - nRangeLow;
+ dassert(nRange > 0);
+ int value = nValue - nRangeLow;
+ int width = tilesiz[nSliderTile].x-8;
+ int cursorX = sliderX + ksgn(nStep)*(value * width / nRange - width / 2);
+ rotatesprite(cursorX<<16, (m_nY+height/2)<<16, 65536, 0, nCursorTile, shade2, pal, 10, 0, 0, xdim-1, ydim-1);
+
+ buffer[0] = 0;
+ switch (nShowValue)
+ {
+ case kMenuSliderNone:
+ break;
+ case kMenuSliderValue:
+ sprintf(buffer, "%i ", nValue);
+ break;
+ case kMenuSliderPercent:
+ sprintf(buffer, "%i%% ", roundscale(value, 100, nRange));
+ break;
+ case kMenuSliderQ16:
+ snprintf(buffer, 16, "%.3f ", nValue/65536.f);
+ break;
+ }
+ int valueWidth;
+ gMenuTextMgr.GetFontInfo(m_nFont, buffer, &valueWidth, NULL);
+ int valueX = m_nX+m_nWidth-1-tilesiz[nSliderTile].x-valueWidth;
+ gMenuTextMgr.DrawText(buffer, m_nFont, valueX, m_nY, 32, 0, false);
+
+ int mx = m_nX;
+ int my = m_nY;
+ int mw = m_nWidth;
+ int mh = height;
+ if (height < tilesiz[nSliderTile].y)
+ {
+ my -= (tilesiz[nSliderTile].y-height)/2;
+ height = tilesiz[nSliderTile].y;
+ }
+ mx <<= 16;
+ my <<= 16;
+ mw <<= 16;
+ mh <<= 16;
+
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && (g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD))
+ {
+ pMenu->SetFocusItem(this);
+
+ int sliderx = m_nX+m_nWidth-1-tilesiz[nSliderTile].x;
+ int sliderwidth = tilesiz[nSliderTile].x;
+ int regionwidth = sliderwidth-8;
+ int regionx = sliderx+(sliderwidth-regionwidth)/2;
+ sliderx <<= 16;
+ sliderwidth <<= 16;
+ regionwidth <<= 16;
+ regionx <<= 16;
+
+ // region between the x-midline of the slidepoint at the extremes slides proportionally
+ if (!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, regionx, my, regionwidth, mh))
+ {
+ int dx = (gGameMenuMgr.m_mousepos.x - (regionx+regionwidth/2))*ksgn(nStep);
+ nValue = nRangeLow + roundscale(dx+regionwidth/2, nRange, regionwidth);
+ nValue = ClipRange(nValue, nRangeLow, nRangeHigh);
+ if (pCallback)
+ pCallback(this);
+ gGameMenuMgr.m_mousecaught = 1;
+ }
+ // region outside the x-midlines clamps to the extremes
+ else if (!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, sliderx, my, sliderwidth, mh))
+ {
+ if ((gGameMenuMgr.m_mousepos.x-(regionx+regionwidth/2))*ksgn(nStep) > 0)
+ nValue = nRangeHigh;
+ else
+ nValue = nRangeLow;
+ if (pCallback)
+ pCallback(this);
+ gGameMenuMgr.m_mousecaught = 1;
+ }
+ }
+ }
+}
+
+bool CGameMenuItemSlider::Event(CGameMenuEvent &event)
+{
+ nValue = pValue ? *pValue : nValue;
+ switch (event.at0)
+ {
+ case kMenuEventUp:
+ pMenu->FocusPrevItem();
+ return false;
+ case kMenuEventDown:
+ pMenu->FocusNextItem();
+ return false;
+ case kMenuEventLeft:
+ if (nStep > 0)
+ nValue = DecBy(nValue, nStep);
+ else
+ nValue = IncBy(nValue, -nStep);
+ nValue = ClipRange(nValue, nRangeLow, nRangeHigh);
+ if (pCallback)
+ pCallback(this);
+ return false;
+ case kMenuEventRight:
+ if (nStep >= 0)
+ nValue = IncBy(nValue, nStep);
+ else
+ nValue = DecBy(nValue, -nStep);
+ nValue = ClipRange(nValue, nRangeLow, nRangeHigh);
+ if (pCallback)
+ pCallback(this);
+ return false;
+ case kMenuEventEnter:
+ if (pCallback)
+ pCallback(this);
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+bool CGameMenuItemSlider::MouseEvent(CGameMenuEvent &event)
+{
+ event.at0 = kMenuEventNone;
+ if (MOUSEINACTIVECONDITIONAL((MOUSE_GetButtons()&LEFT_MOUSE) && (MOUSE_GetButtons()&WHEELUP_MOUSE)))
+ {
+ MOUSE_ClearButton(WHEELUP_MOUSE);
+ event.at0 = kMenuEventLeft;
+ }
+ else if (MOUSEINACTIVECONDITIONAL((MOUSE_GetButtons()&LEFT_MOUSE) && (MOUSE_GetButtons()&WHEELDOWN_MOUSE)))
+ {
+ MOUSE_ClearButton(WHEELDOWN_MOUSE);
+ event.at0 = kMenuEventRight;
+ }
+ else if (MOUSE_GetButtons()&RIGHT_MOUSE)
+ {
+ MOUSE_ClearButton(RIGHT_MOUSE);
+ event.at0 = kMenuEventEscape;
+ }
+ else if (MOUSE_GetButtons()&WHEELUP_MOUSE)
+ {
+ MOUSE_ClearButton(WHEELUP_MOUSE);
+ MOUSE_ClearButton(LEFT_MOUSE);
+ event.at0 = kMenuEventUp;
+ }
+ else if (MOUSE_GetButtons()&WHEELDOWN_MOUSE)
+ {
+ MOUSE_ClearButton(WHEELDOWN_MOUSE);
+ MOUSE_ClearButton(LEFT_MOUSE);
+ event.at0 = kMenuEventDown;
+ }
+ return event.at0 != kMenuEventNone;
+}
+
+CGameMenuItemSliderFloat::CGameMenuItemSliderFloat()
+{
+ m_pzText = NULL;
+ m_nFont = -1;
+ m_nX = 0;
+ m_nY = 0;
+ fValue = 0;
+ fRangeLow = 0;
+ fStep = 0;
+ pCallback = NULL;
+ pValue = NULL;
+ nSliderTile = 2204;
+ nCursorTile = 2028;
+ nShowValue = kMenuSliderNone;
+}
+
+CGameMenuItemSliderFloat::CGameMenuItemSliderFloat(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, float _fValue, float _fRangeLow, float _fRangeHigh, float _fStep, void(*_pCallback)(CGameMenuItemSliderFloat *), int _nSliderTile, int _nCursorTile, int _nShowValue)
+{
+ m_pzText = _pzText;
+ m_nFont = _nFont;
+ m_nX = _nX;
+ m_nY = _nY;
+ m_nWidth = _nWidth;
+ fRangeLow = _fRangeLow;
+ fRangeHigh = _fRangeHigh;
+ fStep = _fStep;
+ fValue = ClipRangeF(_fValue, fRangeLow, fRangeHigh);
+ pCallback = _pCallback;
+ nSliderTile = 2204;
+ nCursorTile = 2028;
+ if (_nSliderTile >= 0)
+ nSliderTile = _nSliderTile;
+ if (_nCursorTile >= 0)
+ nCursorTile = _nCursorTile;
+ nShowValue = _nShowValue;
+}
+
+CGameMenuItemSliderFloat::CGameMenuItemSliderFloat(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, float *pnValue, float _fRangeLow, float _fRangeHigh, float _fStep, void(*_pCallback)(CGameMenuItemSliderFloat *), int _nSliderTile, int _nCursorTile, int _nShowValue)
+{
+ m_pzText = _pzText;
+ m_nFont = _nFont;
+ m_nX = _nX;
+ m_nY = _nY;
+ m_nWidth = _nWidth;
+ fRangeLow = _fRangeLow;
+ fRangeHigh = _fRangeHigh;
+ fStep = _fStep;
+ dassert(pnValue != NULL);
+ pValue = pnValue;
+ fValue = ClipRangeF(*pnValue, fRangeLow, fRangeHigh);
+ pCallback = _pCallback;
+ nSliderTile = 2204;
+ nCursorTile = 2028;
+ if (_nSliderTile >= 0)
+ nSliderTile = _nSliderTile;
+ if (_nCursorTile >= 0)
+ nCursorTile = _nCursorTile;
+ nShowValue = _nShowValue;
+}
+
+void CGameMenuItemSliderFloat::Draw(void)
+{
+ char buffer[16];
+ int height;
+
+ fValue = pValue ? *pValue : fValue;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ int shade = bEnable ? 32 : 48;
+ int shade2 = bEnable ? 0 : 16;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ if (m_pzText)
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, m_nX, m_nY, shade, pal, false);
+ int sliderX = m_nX+m_nWidth-1-tilesiz[nSliderTile].x/2;
+ rotatesprite(sliderX<<16, (m_nY+height/2)<<16, 65536, 0, nSliderTile, shade2, pal, 10, 0, 0, xdim-1, ydim-1);
+ float fRange = fRangeHigh - fRangeLow;
+ dassert(fRange > 0);
+ float value = fValue - fRangeLow;
+ int width = tilesiz[nSliderTile].x-8;
+ int cursorX = sliderX + (int)(ksgnf(fStep)*(value * width / fRange - width / 2));
+ rotatesprite(cursorX<<16, (m_nY+height/2)<<16, 65536, 0, nCursorTile, shade2, pal, 10, 0, 0, xdim-1, ydim-1);
+
+ buffer[0] = 0;
+ switch (nShowValue)
+ {
+ case kMenuSliderNone:
+ break;
+ case kMenuSliderValue:
+ snprintf(buffer, 16, "%.3f ", fValue);
+ break;
+ case kMenuSliderPercent:
+ snprintf(buffer, 16, "%.3f%% ", value*100.f/fRange);
+ break;
+ }
+ int valueWidth;
+ gMenuTextMgr.GetFontInfo(m_nFont, buffer, &valueWidth, NULL);
+ int valueX = m_nX+m_nWidth-1-tilesiz[nSliderTile].x-valueWidth;
+ gMenuTextMgr.DrawText(buffer, m_nFont, valueX, m_nY, 32, 0, false);
+
+ int mx = m_nX;
+ int my = m_nY;
+ int mw = m_nWidth;
+ int mh = height;
+ if (height < tilesiz[nSliderTile].y)
+ {
+ my -= (tilesiz[nSliderTile].y-height)/2;
+ height = tilesiz[nSliderTile].y;
+ }
+ mx <<= 16;
+ my <<= 16;
+ mw <<= 16;
+ mh <<= 16;
+
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && (g_mouseClickState == MOUSE_PRESSED || g_mouseClickState == MOUSE_HELD))
+ {
+ pMenu->SetFocusItem(this);
+
+ int sliderx = m_nX+m_nWidth-1-tilesiz[nSliderTile].x;
+ int sliderwidth = tilesiz[nSliderTile].x;
+ int regionwidth = sliderwidth-8;
+ int regionx = sliderx+(sliderwidth-regionwidth)/2;
+ sliderx <<= 16;
+ sliderwidth <<= 16;
+ regionwidth <<= 16;
+ regionx <<= 16;
+
+ // region between the x-midline of the slidepoint at the extremes slides proportionally
+ if (!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, regionx, my, regionwidth, mh))
+ {
+ int dx = (gGameMenuMgr.m_mousepos.x - (regionx+regionwidth/2))*ksgnf(fStep);
+ fValue = fRangeLow + (dx+regionwidth/2) * fRange / regionwidth;
+ fValue = ClipRangeF(fValue, fRangeLow, fRangeHigh);
+ if (pCallback)
+ pCallback(this);
+ gGameMenuMgr.m_mousecaught = 1;
+ }
+ // region outside the x-midlines clamps to the extremes
+ else if (!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, sliderx, my, sliderwidth, mh))
+ {
+ if ((gGameMenuMgr.m_mousepos.x-(regionx+regionwidth/2))*ksgnf(fStep) > 0)
+ fValue = fRangeHigh;
+ else
+ fValue = fRangeLow;
+ if (pCallback)
+ pCallback(this);
+ gGameMenuMgr.m_mousecaught = 1;
+ }
+ }
+ }
+}
+
+bool CGameMenuItemSliderFloat::Event(CGameMenuEvent &event)
+{
+ fValue = pValue ? *pValue : fValue;
+ switch (event.at0)
+ {
+ case kMenuEventUp:
+ pMenu->FocusPrevItem();
+ return false;
+ case kMenuEventDown:
+ pMenu->FocusNextItem();
+ return false;
+ case kMenuEventLeft:
+ if (fStep > 0)
+ fValue -= fStep;
+ else
+ fValue += fStep;
+ fValue = ClipRangeF(fValue, fRangeLow, fRangeHigh);
+ if (pCallback)
+ pCallback(this);
+ return false;
+ case kMenuEventRight:
+ if (fStep >= 0)
+ fValue += fStep;
+ else
+ fValue -= fStep;
+ fValue = ClipRangeF(fValue, fRangeLow, fRangeHigh);
+ if (pCallback)
+ pCallback(this);
+ return false;
+ case kMenuEventEnter:
+ if (pCallback)
+ pCallback(this);
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemZEdit::CGameMenuItemZEdit()
+{
+ m_pzText = NULL;
+ m_nFont = -1;
+ m_nX = 0;
+ m_nY = 0;
+ at20 = NULL;
+ at24 = 0;
+ at32 = 0;
+ at2c = 0;
+ at30 = 0;
+ at28 = 0;
+ at31 = 1;
+}
+
+CGameMenuItemZEdit::CGameMenuItemZEdit(const char *a1, int a2, int a3, int a4, int a5, char *a6, int a7, char a8, void(*a9)(CGameMenuItemZEdit *, CGameMenuEvent *), int a10)
+{
+ at30 = 0;
+ at31 = 1;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ at20 = a6;
+ at24 = a7;
+ at32 = a8;
+ at2c = a9;
+ at28 = a10;
+}
+
+void CGameMenuItemZEdit::AddChar(char ch)
+{
+ int i = strlen(at20);
+ if (i + 1 < at24)
+ {
+ at20[i] = ch;
+ at20[i + 1] = 0;
+ }
+}
+
+void CGameMenuItemZEdit::BackChar(void)
+{
+ int i = strlen(at20);
+ if (i > 0)
+ at20[i - 1] = 0;
+}
+
+void CGameMenuItemZEdit::Draw(void)
+{
+ int height, width, textWidth = 0;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, &width, &height);
+ if (at20)
+ gMenuTextMgr.GetFontInfo(m_nFont, at20, &textWidth, NULL);
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ if (at30)
+ shade = -128;
+ if (m_pzText)
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, m_nX, m_nY, shade, pal, false);
+ int x = m_nX+m_nWidth-1-textWidth;//(at24+1)*width;
+ if (at20 && *at20)
+ {
+ int width;
+ gMenuTextMgr.GetFontInfo(m_nFont, at20, &width, NULL);
+ int shade2;
+ if (at32)
+ {
+ if (at30)
+ shade2 = -128;
+ else
+ shade2 = shade;
+ }
+ else
+ {
+ if (at30)
+ shade2 = shade;
+ else
+ shade2 = 32;
+ }
+ gMenuTextMgr.DrawText(at20, m_nFont, x, m_nY, shade2, pal, false);
+ x += width;
+ }
+ if (at30 && (gGameClock & 32))
+ gMenuTextMgr.DrawText("_", m_nFont, x, m_nY, shade, 0, false);
+
+ int mx = m_nX<<16;
+ int my = m_nY<<16;
+ int mw = m_nWidth<<16;
+ int mh = height<<16;
+
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, mx, my, mw, mh))
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+ }
+}
+
+bool CGameMenuItemZEdit::Event(CGameMenuEvent &event)
+{
+ static char buffer[256];
+ // Hack
+ if (event.at2 == sc_kpad_2 || event.at2 == sc_kpad_4 || event.at2 == sc_kpad_6 || event.at2 == sc_kpad_8)
+ event.at0 = kMenuEventKey;
+ switch (event.at0)
+ {
+ case kMenuEventEscape:
+ if (at30)
+ {
+ strncpy(at20, buffer, at24);
+ at20[at24-1] = 0;
+ at30 = 0;
+ return false;
+ }
+ return true;
+ case kMenuEventEnter:
+ if (!at31)
+ {
+ if (at2c)
+ at2c(this, &event);
+ return false;
+ }
+ if (at30)
+ {
+ if (at2c)
+ at2c(this, &event);
+ at30 = 0;
+ return false;
+ }
+ strncpy(buffer, at20, at24);
+ buffer[at24-1] = 0;
+ at30 = 1;
+ return false;
+ case kMenuEventBackSpace:
+ if (at30)
+ BackChar();
+ return false;
+ case kMenuEventKey:
+ case kMenuEventSpace:
+ {
+ char key;
+ if (event.at2 < 128)
+ {
+ if (keystatus[sc_LeftShift] || keystatus[sc_RightShift])
+ key = g_keyAsciiTableShift[event.at2];
+ else
+ key = g_keyAsciiTable[event.at2];
+ if (at30 && (isalnum(key) || ispunct(key) || isspace(key)))
+ {
+ AddChar(key);
+ return false;
+ }
+ }
+ return CGameMenuItem::Event(event);
+ }
+ case kMenuEventUp:
+ if (at30)
+ return false;
+ return CGameMenuItem::Event(event);
+ case kMenuEventDown:
+ if (at30)
+ return false;
+ return CGameMenuItem::Event(event);
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemZEditBitmap::CGameMenuItemZEditBitmap()
+{
+ m_pzText = NULL;
+ m_nFont = -1;
+ m_nX = 0;
+ m_nY = 0;
+ at20 = NULL;
+ at24 = 0;
+ at36 = 0;
+ at30 = NULL;
+ at2c = NULL;
+ at34 = 0;
+ at28 = 0;
+ at37 = 0;
+ at35 = 1;
+}
+
+CGameMenuItemZEditBitmap::CGameMenuItemZEditBitmap(char *a1, int a2, int a3, int a4, int a5, char *a6, int a7, char a8, void(*a9)(CGameMenuItemZEditBitmap *, CGameMenuEvent *), int a10)
+{
+ at2c = NULL;
+ at34 = 0;
+ at35 = 1;
+ at37 = 0;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ at20 = a6;
+ at24 = a7;
+ at36 = a8;
+ at30 = a9;
+ at28 = a10;
+}
+
+void CGameMenuItemZEditBitmap::AddChar(char ch)
+{
+ int i = strlen(at20);
+ if (i + 1 < at24)
+ {
+ at20[i] = ch;
+ at20[i + 1] = 0;
+ }
+}
+
+void CGameMenuItemZEditBitmap::BackChar(void)
+{
+ int i = strlen(at20);
+ if (i > 0)
+ at20[i - 1] = 0;
+}
+
+void CGameMenuItemZEditBitmap::Draw(void)
+{
+ int height, width;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, &width, &height);
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ at2c->at24 = -1;
+ if (at34)
+ shade = -128;
+ if (m_pzText)
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, m_nX, m_nY, shade, pal, false);
+ int x = m_nX+m_nWidth-1-(at24+1)*width;
+ if (at20 && *at20)
+ {
+ int width;
+ gMenuTextMgr.GetFontInfo(m_nFont, at20, &width, NULL);
+ int shade2;
+ if (at36)
+ {
+ if (at34)
+ shade2 = -128;
+ else
+ shade2 = shade;
+ }
+ else
+ {
+ if (at34)
+ shade2 = shade;
+ else
+ shade2 = 32;
+ }
+ gMenuTextMgr.DrawText(at20, m_nFont, x, m_nY, shade2, 0, false);
+ x += width;
+ }
+ if (at34 && (gGameClock & 32))
+ gMenuTextMgr.DrawText("_", m_nFont, x, m_nY, shade, pal, false);
+
+ int mx = m_nX<<16;
+ int my = m_nY<<16;
+ int mw = m_nWidth<<16;
+ int mh = height<<16;
+
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, mx, my, mw, mh))
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+ }
+}
+
+bool CGameMenuItemZEditBitmap::Event(CGameMenuEvent &event)
+{
+ static char buffer[256];
+ // Hack
+ if (event.at2 == sc_kpad_2 || event.at2 == sc_kpad_4 || event.at2 == sc_kpad_6 || event.at2 == sc_kpad_8)
+ event.at0 = kMenuEventKey;
+ switch (event.at0)
+ {
+ case kMenuEventEscape:
+ if (at34)
+ {
+ strncpy(at20, buffer, at24);
+ at20[at24-1] = 0;
+ at34 = 0;
+ gSaveGameActive = false;
+ return false;
+ }
+ gSaveGameActive = true;
+ return true;
+ case kMenuEventEnter:
+ if (!at35)
+ {
+ if (at30)
+ at30(this, &event);
+ gSaveGameActive = false;
+ return false;
+ }
+ if (at34)
+ {
+ if (at30)
+ at30(this, &event);
+ at34 = 0;
+ gSaveGameActive = false;
+ return false;
+ }
+ strncpy(buffer, at20, at24);
+ if (at37)
+ at20[0] = 0;
+ buffer[at24-1] = 0;
+ at34 = 1;
+ return false;
+ case kMenuEventBackSpace:
+ if (at34)
+ BackChar();
+ return false;
+ case kMenuEventKey:
+ case kMenuEventSpace:
+ {
+ char key;
+ if (event.at2 < 128)
+ {
+ if (keystatus[sc_LeftShift] || keystatus[sc_RightShift])
+ key = g_keyAsciiTableShift[event.at2];
+ else
+ key = g_keyAsciiTable[event.at2];
+ if (at30 && (isalnum(key) || ispunct(key) || isspace(key)))
+ {
+ AddChar(key);
+ return false;
+ }
+ }
+ return CGameMenuItem::Event(event);
+ }
+ case kMenuEventUp:
+ if (at34)
+ return false;
+ return CGameMenuItem::Event(event);
+ case kMenuEventDown:
+ if (at34)
+ return false;
+ return CGameMenuItem::Event(event);
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemQAV::CGameMenuItemQAV()
+{
+ at20 = NULL;
+ at24 = NULL;
+ at28 = 0;
+ bEnable = 0;
+}
+
+CGameMenuItemQAV::CGameMenuItemQAV(const char *a1, int a2, int a3, int a4, const char *a5, bool widescreen, bool clearbackground)
+{
+ m_nWidth = 0;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nY = a4;
+ at20 = a5;
+ m_nX = a3;
+ bEnable = 0;
+ bWideScreen = widescreen;
+ bClearBackground = clearbackground;
+}
+
+void CGameMenuItemQAV::Draw(void)
+{
+ if (bClearBackground)
+ videoClearScreen(0);
+ if (at24)
+ {
+ int backFC = gFrameClock;
+ gFrameClock = gGameClock;
+ int nTicks = totalclock - at30;
+ at30 = totalclock;
+ at2c -= nTicks;
+ if (at2c <= 0 || at2c > at28->at10)
+ {
+ at2c = at28->at10;
+ }
+ at28->Play(at28->at10 - at2c - nTicks, at28->at10 - at2c, -1, NULL);
+ int wx1, wy1, wx2, wy2;
+ wx1 = windowxy1.x;
+ wy1 = windowxy1.y;
+ wx2 = windowxy2.x;
+ wy2 = windowxy2.y;
+ windowxy1.x = 0;
+ windowxy1.y = 0;
+ windowxy2.x = xdim-1;
+ windowxy2.y = ydim-1;
+ if (bWideScreen)
+ {
+ int xdim43 = scale(ydim, 4, 3);
+ int nCount = (xdim+xdim43-1)/xdim43;
+ int backX = at28->x;
+ for (int i = 0; i < nCount; i++)
+ {
+ at28->Draw(at28->at10 - at2c, 10+kQavOrientationLeft, 0, 0);
+ at28->x += 320;
+ }
+ at28->x = backX;
+ }
+ else
+ at28->Draw(at28->at10 - at2c, 10, 0, 0);
+
+ windowxy1.x = wx1;
+ windowxy1.y = wy1;
+ windowxy2.x = wx2;
+ windowxy2.y = wy2;
+ gFrameClock = backFC;
+ }
+
+ if (bEnable && !gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED)
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+}
+
+bool CGameMenuItemQAV::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventLeft:
+ case kMenuEventBackSpace:
+ pMenu->FocusPrevItem();
+ return false;
+ case kMenuEventRight:
+ case kMenuEventEnter:
+ case kMenuEventSpace:
+ pMenu->FocusNextItem();
+ return false;
+ case kMenuEventInit:
+ if (at20)
+ {
+ if (!at28)
+ {
+ at24 = gSysRes.Lookup(at20, "QAV");
+ if (!at24)
+ ThrowError("Could not load QAV %s\n", at20);
+ at28 = (QAV*)gSysRes.Lock(at24);
+ at28->nSprite = -1;
+ at28->x = m_nX;
+ at28->y = m_nY;
+ at28->Preload();
+ at2c = at28->at10;
+ at30 = totalclock;
+ return false;
+ }
+ gSysRes.Lock(at24);
+ }
+ return false;
+ case kMenuEventDeInit:
+ if (at20 && at28)
+ {
+ gSysRes.Unlock(at24);
+ if (at24->lockCount == 0)
+ at28 = NULL;
+ }
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+void CGameMenuItemQAV::Reset(void)
+{
+ at2c = at28->at10;
+ at30 = totalclock;
+}
+
+CGameMenuItemZCycleSelect::CGameMenuItemZCycleSelect()
+{
+ m_pzText = NULL;
+ m_nFont = 3;
+ m_nX = 0;
+ m_nY = 0;
+ m_nRows = 0;
+ m_nTopDelta = 0;
+ m_nFocus = 0;
+ m_nItems = 0;
+ m_pzStrings = NULL;
+ m_pReturn = NULL;
+}
+
+CGameMenuItemZCycleSelect::CGameMenuItemZCycleSelect(const char *pzText, int nFont, int nX, int nY, int nWidth, int nRows, int nItems, const char **pzStrings, int *pReturn, void(*pCallback)(CGameMenuItemZCycleSelect *))
+{
+ m_nTopDelta = 0;
+ m_nFocus = 0;
+ m_pzText = pzText;
+ m_nFont = nFont;
+ m_nX = nX;
+ m_nY = nY;
+ m_nWidth = nWidth;
+ m_nRows = nRows;
+ m_pCallback = pCallback;
+ m_nItems = nItems;
+ m_pzStrings = pzStrings;
+ m_pReturn = pReturn;
+}
+
+void CGameMenuItemZCycleSelect::Draw(void)
+{
+ int height;
+ int shade;
+ gMenuTextMgr.GetFontInfo(m_nFont, NULL, NULL, &height);
+ int y = m_nY;
+ int k = m_nFocus - m_nTopDelta;
+ int nNewFocus = m_nFocus;
+ bool bClick = false;
+ for (int i = 0; i < m_nRows; i++, y += height, k++)
+ {
+ if (k == m_nFocus)
+ {
+ shade = 32;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ viewDrawText(3, m_pzStrings[k], m_nX, y, shade, 0, 0, false);
+ }
+ else
+ {
+ viewDrawText(3, m_pzStrings[k], m_nX, y, 24, 0, 0, false);
+ }
+ int mx = m_nX<<16;
+ int my = y<<16;
+ int mw = m_nWidth<<16;
+ int mh = height<<16;
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, mx, my, mw, mh)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, mx, my, mw, mh)))
+ {
+ nNewFocus = k;
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, mx, my, mw, mh))
+ {
+ nNewFocus = k;
+ bClick = true;
+ }
+ }
+ }
+ m_nTopDelta += nNewFocus-m_nFocus;
+ m_nFocus = nNewFocus;
+ if (bClick)
+ {
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+}
+
+bool CGameMenuItemZCycleSelect::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventUp:
+ if (event.at2 == sc_Tab || m_nFocus == 0)
+ {
+ pMenu->FocusPrevItem();
+ return false;
+ }
+ m_nFocus--;
+ if (m_nTopDelta > 0)
+ m_nTopDelta--;
+ return false;
+ case kMenuEventDown:
+ if (event.at2 == sc_Tab || m_nFocus == m_nItems-1)
+ {
+ pMenu->FocusNextItem();
+ return false;
+ }
+ m_nFocus++;
+ if (m_nTopDelta+1 < m_nRows)
+ m_nTopDelta++;
+ return false;
+ case kMenuEventEnter:
+ if (m_pCallback)
+ m_pCallback(this);
+ *m_pReturn = m_nFocus;
+ return true;
+ case kMenuEventScrollUp:
+ if (m_nFocus-m_nTopDelta > 0)
+ {
+ m_nTopDelta++;
+ if (m_nTopDelta>0)
+ {
+ m_nFocus--;
+ m_nTopDelta--;
+ }
+ }
+ return false;
+ case kMenuEventScrollDown:
+ if (m_nFocus-m_nTopDelta+m_nRows < m_nItems)
+ {
+ m_nTopDelta--;
+ if (m_nTopDelta+1 < m_nRows)
+ {
+ m_nFocus++;
+ m_nTopDelta++;
+ }
+ }
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+bool CGameMenuItemZCycleSelect::MouseEvent(CGameMenuEvent &event)
+{
+ event.at0 = kMenuEventNone;
+ if (MOUSEACTIVECONDITIONAL(MOUSE_GetButtons()&WHEELUP_MOUSE))
+ {
+ gGameMenuMgr.m_mouselastactivity = totalclock;
+ MOUSE_ClearButton(WHEELUP_MOUSE);
+ event.at0 = kMenuEventScrollUp;
+ }
+ else if (MOUSEACTIVECONDITIONAL(MOUSE_GetButtons()&WHEELDOWN_MOUSE))
+ {
+ gGameMenuMgr.m_mouselastactivity = totalclock;
+ MOUSE_ClearButton(WHEELDOWN_MOUSE);
+ event.at0 = kMenuEventScrollDown;
+ }
+ else
+ return CGameMenuItem::MouseEvent(event);
+ return event.at0 != kMenuEventNone;
+}
+
+CGameMenuItemZCycle::CGameMenuItemZCycle()
+{
+ m_pzText = NULL;
+ m_nFocus = 0;
+ m_nItems = 0;
+ m_pCallback = NULL;
+ m_pCallbackSelect = NULL;
+ m_pMenuSelect = NULL;
+ m_pItemSelectTitle = NULL;
+ m_pItemSelect = NULL;
+ m_nMenuSelectReturn = -1;
+}
+
+CGameMenuItemZCycle::CGameMenuItemZCycle(const char *a1, int a2, int a3, int a4, int a5, int a6, void(*a7)(CGameMenuItemZCycle *), const char **a8, int a9, int a10, bool bMenu, void(*pCallbackSelect)(CGameMenuItemZCycleSelect*))
+{
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nFocus = 0;
+ m_nWidth = a5;
+ m_nAlign = a6;
+ m_pCallback = a7;
+ m_pCallbackSelect = pCallbackSelect;
+ m_nItems = 0;
+ m_bMenu = bMenu;
+ m_pMenuSelect = NULL;
+ m_pItemSelectTitle = NULL;
+ m_pItemSelect = NULL;
+ m_nMenuSelectReturn = -1;
+ SetTextArray(a8, a9, a10);
+}
+
+CGameMenuItemZCycle::~CGameMenuItemZCycle()
+{
+ m_pzText = NULL;
+ m_nFocus = 0;
+ m_nItems = 0;
+ m_pCallback = NULL;
+ m_pCallbackSelect = NULL;
+ m_pMenuSelect = NULL;
+ m_pItemSelectTitle = NULL;
+ m_pItemSelect = NULL;
+ m_nMenuSelectReturn = -1;
+ memset(m_pzStrings, 0, sizeof(m_pzStrings));
+}
+
+void CGameMenuItemZCycle::Draw(void)
+{
+ int width = 0, height = 0;
+ int shade = bEnable ? 32 : 48;
+ int pal = bEnable ? 0 : 5;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int x = m_nX;
+ int y = m_nY;
+
+ if (m_nMenuSelectReturn != -1)
+ {
+ m_nFocus = m_nMenuSelectReturn;
+ if (m_pCallback)
+ m_pCallback(this);
+ m_nMenuSelectReturn = -1;
+ }
+
+ if (m_pzText)
+ {
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, &height);
+ switch (m_nAlign)
+ {
+ case 1:
+ x = m_nX+m_nWidth/2-width/2;
+ break;
+ case 2:
+ x = m_nX+m_nWidth-1-width;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, y, shade, pal, false);
+ }
+ const char *pzText;
+ if (!m_nItems)
+ pzText = "????";
+ else
+ pzText = m_pzStrings[m_nFocus];
+ dassert(pzText != NULL);
+ gMenuTextMgr.GetFontInfo(m_nFont, pzText, &width, NULL);
+ gMenuTextMgr.DrawText(pzText, m_nFont, m_nX + m_nWidth - 1 - width, y, shade, pal, false);
+ if (bEnable && MOUSEACTIVECONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousepos, x<<16, y<<16, m_nWidth<<16, height<<16)))
+ {
+ if (MOUSEWATCHPOINTCONDITIONAL(!gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_prevmousepos, x<<16, y<<16, m_nWidth<<16, height<<16)))
+ {
+ pMenu->SetFocusItem(this);
+ }
+
+ if (!gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED && !gGameMenuMgr.MouseOutsideBounds(&gGameMenuMgr.m_mousedownpos, x<<16, y<<16, m_nWidth<<16, height<<16))
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+ }
+}
+
+bool CGameMenuItemZCycle::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ if (m_bMenu)
+ {
+ if (m_pMenuSelect)
+ {
+ delete m_pMenuSelect;
+ m_pMenuSelect = NULL;
+ }
+ if (m_pItemSelectTitle)
+ {
+ delete m_pItemSelectTitle;
+ m_pItemSelectTitle = NULL;
+ }
+ if (m_pItemSelect)
+ {
+ delete m_pItemSelect;
+ m_pItemSelect = NULL;
+ }
+ m_pMenuSelect = new CGameMenu();
+ dassert(m_pMenuSelect != NULL);
+ strncpy(m_zTitle, m_pzText, kMaxTitleLength);
+ int l = strlen(m_zTitle);
+ if (l > 0 && m_zTitle[l-1] == ':')
+ l--;
+ m_zTitle[l] = 0;
+ m_pItemSelectTitle = new CGameMenuItemTitle(m_zTitle, 1, 160, 20, 2038);
+ dassert(m_pItemSelectTitle != NULL);
+ m_pItemSelect = new CGameMenuItemZCycleSelect("", 3, 100, 40, 100, 16, m_nItems, m_pzStrings, &m_nMenuSelectReturn, m_pCallbackSelect);
+ dassert(m_pItemSelect != NULL);
+ m_pMenuSelect->Add(m_pItemSelectTitle, false);
+ m_pMenuSelect->Add(m_pItemSelect, true);
+ m_pMenuSelect->Add(&itemBloodQAV, false);
+ gGameMenuMgr.Push(m_pMenuSelect, -1);
+ return false;
+ }
+ fallthrough__;
+ case kMenuEventRight:
+ case kMenuEventSpace:
+ Next();
+ if (m_pCallback)
+ m_pCallback(this);
+ return false;
+ case kMenuEventLeft:
+ Prev();
+ if (m_pCallback)
+ m_pCallback(this);
+ return false;
+ case kMenuEventDeInit:
+ if (m_pMenuSelect)
+ {
+ delete m_pMenuSelect;
+ m_pMenuSelect = NULL;
+ }
+ if (m_pItemSelectTitle)
+ {
+ delete m_pItemSelectTitle;
+ m_pItemSelectTitle = NULL;
+ }
+ if (m_pItemSelect)
+ {
+ delete m_pItemSelect;
+ m_pItemSelect = NULL;
+ }
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+void CGameMenuItemZCycle::Add(const char *pItem, bool active)
+{
+ dassert(pItem != NULL);
+ dassert(m_nItems < kMaxGameCycleItems);
+ m_pzStrings[m_nItems] = pItem;
+ if (active)
+ m_nFocus = m_nItems;
+ m_nItems++;
+}
+
+void CGameMenuItemZCycle::Next(void)
+{
+ if (m_nItems > 0)
+ {
+ m_nFocus++;
+ if (m_nFocus >= m_nItems)
+ m_nFocus = 0;
+ }
+}
+
+void CGameMenuItemZCycle::Prev(void)
+{
+ if (m_nItems > 0)
+ {
+ m_nFocus--;
+ if (m_nFocus < 0)
+ m_nFocus += m_nItems;
+ }
+}
+
+void CGameMenuItemZCycle::Clear(void)
+{
+ m_nItems = m_nFocus = 0;
+ memset(m_pzStrings, 0, sizeof(m_pzStrings));
+}
+
+void CGameMenuItemZCycle::SetTextArray(const char **pTextArray, int nTextPtrCount, int nIndex)
+{
+ Clear();
+ dassert(nTextPtrCount <= kMaxGameCycleItems);
+ for (int i = 0; i < nTextPtrCount; i++)
+ Add(pTextArray[i], false);
+ SetTextIndex(nIndex);
+}
+
+void CGameMenuItemZCycle::SetTextIndex(int nIndex)
+{
+ m_nFocus = ClipRange(nIndex, 0, m_nItems);
+}
+
+CGameMenuItemYesNoQuit::CGameMenuItemYesNoQuit()
+{
+ m_pzText = NULL;
+ m_nRestart = 0;
+}
+
+CGameMenuItemYesNoQuit::CGameMenuItemYesNoQuit(const char *a1, int a2, int a3, int a4, int a5, int a6, int a7)
+{
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+ m_nWidth = a5;
+ at20 = a6;
+ m_nRestart = a7;
+}
+
+void CGameMenuItemYesNoQuit::Draw(void)
+{
+ if (!m_pzText) return;
+ int shade = 32;
+ if (pMenu->IsFocusItem(this))
+ shade = 32-(totalclock&63);
+ int width;
+ int x = m_nX;
+ switch (at20)
+ {
+ case 1:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX+m_nWidth/2-width/2;
+ break;
+ case 2:
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ x = m_nX+m_nWidth-1-width;
+ break;
+ case 0:
+ default:
+ break;
+ }
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, x, m_nY, shade, 0, true);
+
+ if (bEnable && !gGameMenuMgr.m_mousecaught && g_mouseClickState == MOUSE_RELEASED)
+ {
+ pMenu->SetFocusItem(this);
+
+ CGameMenuEvent event = { kMenuEventEnter, 0 };
+
+ gGameMenuMgr.m_mousecaught = 1;
+
+ if (Event(event))
+ gGameMenuMgr.PostPop();
+ }
+}
+
+extern void Restart(CGameMenuItemChain *pItem);
+extern void Quit(CGameMenuItemChain *pItem);
+
+bool CGameMenuItemYesNoQuit::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventKey:
+ if (event.at2 == sc_Y)
+ {
+ if (m_nRestart)
+ Restart(NULL);
+ else
+ Quit(NULL);
+ }
+ else if (event.at2 == sc_N)
+ gGameMenuMgr.Pop();
+ return false;
+ case kMenuEventEnter:
+ if (m_nRestart)
+ Restart(NULL);
+ else
+ Quit(NULL);
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+CGameMenuItemPicCycle::CGameMenuItemPicCycle()
+{
+ m_pzText = NULL;
+ at24 = 0;
+ m_nItems = 0;
+ atb0 = 0;
+ at2c = 0;
+ atb4 = 0;
+}
+
+CGameMenuItemPicCycle::CGameMenuItemPicCycle(int a1, int a2, void(*a3)(CGameMenuItemPicCycle *), int *a4, int a5, int a6)
+{
+ m_nWidth = 0;
+ at24 = 0;
+ m_nItems = 0;
+ m_nX = a1;
+ m_nY = a2;
+ atb0 = a3;
+ atb4 = 0;
+ SetPicArray(a4, a5, a6);
+}
+
+void CGameMenuItemPicCycle::Draw(void)
+{
+ videoSetViewableArea(0, 0, xdim - 1, ydim - 1);
+ if (atb4)
+ rotatesprite(0, 0, 65536, 0, atb4, 0, 0, 82, 0, 0, xdim - 1, ydim - 1);
+ if (at30[at24])
+ rotatesprite(0, 0, 65536, 0, at30[at24], 0, 0, 82, 0, 0, xdim - 1, ydim - 1);
+}
+
+bool CGameMenuItemPicCycle::Event(CGameMenuEvent &event)
+{
+ switch (event.at0)
+ {
+ case kMenuEventRight:
+ case kMenuEventEnter:
+ case kMenuEventSpace:
+ Next();
+ if (atb0)
+ atb0(this);
+ return false;
+ case kMenuEventLeft:
+ Prev();
+ if (atb0)
+ atb0(this);
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+}
+
+void CGameMenuItemPicCycle::Add(int nItem, bool active)
+{
+ dassert(m_nItems < kMaxPicCycleItems);
+ at30[m_nItems] = nItem;
+ if (active)
+ at24 = m_nItems;
+ m_nItems++;
+}
+
+void CGameMenuItemPicCycle::Next(void)
+{
+ if (m_nItems > 0)
+ {
+ at24++;
+ if (at24 >= m_nItems)
+ at24 = 0;
+ }
+}
+
+void CGameMenuItemPicCycle::Prev(void)
+{
+ if (m_nItems > 0)
+ {
+ at24--;
+ if (at24 < 0)
+ at24 += m_nItems;
+ }
+}
+
+void CGameMenuItemPicCycle::Clear(void)
+{
+ m_nItems = at24 = 0;
+ memset(at30, 0, sizeof(at30));
+ at2c = 0;
+}
+
+void CGameMenuItemPicCycle::SetPicArray(int *pArray, int nTileCount, int nIndex)
+{
+ Clear();
+ at2c = 0;
+ dassert(nTileCount <= kMaxPicCycleItems);
+ for (int i = 0; i < nTileCount; i++)
+ Add(pArray[i], false);
+ SetPicIndex(nIndex);
+}
+
+void CGameMenuItemPicCycle::SetPicIndex(int nIndex)
+{
+ at24 = ClipRange(nIndex, 0, m_nItems);
+}
+
+CGameMenuItemPassword::CGameMenuItemPassword()
+{
+ at37 = 0;
+ m_pzText = NULL;
+ at36 = 0;
+ at32 = 0;
+ at5b = 0;
+}
+
+CGameMenuItemPassword::CGameMenuItemPassword(const char *a1, int a2, int a3, int a4)
+{
+ at37 = 0;
+ m_nWidth = 0;
+ at36 = 0;
+ at32 = 0;
+ at5b = 0;
+ m_pzText = a1;
+ m_nFont = a2;
+ m_nX = a3;
+ m_nY = a4;
+}
+
+const char *kCheckPasswordMsg = "ENTER PASSWORD: ";
+const char *kOldPasswordMsg = "ENTER OLD PASSWORD: ";
+const char *kNewPasswordMsg = "ENTER NEW PASSWORD: ";
+const char *kInvalidPasswordMsg = "INVALID PASSWORD.";
+
+void CGameMenuItemPassword::Draw(void)
+{
+ bool focus = pMenu->IsFocusItem(this);
+ int shade = 32;
+ int shadef = 32-(totalclock&63);
+ int width;
+ switch (at37)
+ {
+ case 1:
+ case 2:
+ case 3:
+ switch (at37)
+ {
+ case 1:
+ strcpy(at3b, kCheckPasswordMsg);
+ break;
+ case 2:
+ strcpy(at3b, kOldPasswordMsg);
+ break;
+ case 3:
+ strcpy(at3b, kNewPasswordMsg);
+ break;
+ }
+ for (int i = 0; i < at32; i++)
+ strcat(at3b, "*");
+ strcat(at3b, "_");
+ gMenuTextMgr.GetFontInfo(m_nFont, at3b, &width, NULL);
+ gMenuTextMgr.DrawText(at3b, m_nFont, m_nX-width/2, m_nY+20, shadef, 0, false);
+ shadef = 32;
+ break;
+ case 4:
+ if ((totalclock - at5b) & 32)
+ {
+ gMenuTextMgr.GetFontInfo(m_nFont, kInvalidPasswordMsg, &width, NULL);
+ gMenuTextMgr.DrawText(kInvalidPasswordMsg, m_nFont, m_nX - width / 2, m_nY + 20, shade, 0, false);
+ }
+ if (at5b && totalclock-at5b > 256)
+ {
+ at5b = 0;
+ at37 = 0;
+ }
+ break;
+ }
+ gMenuTextMgr.GetFontInfo(m_nFont, m_pzText, &width, NULL);
+ gMenuTextMgr.DrawText(m_pzText, m_nFont, m_nX-width/2, m_nY, focus ? shadef : shade, 0, false);
+}
+
+bool CGameMenuItemPassword::Event(CGameMenuEvent &event)
+{
+ switch (at37)
+ {
+ case 0:
+ case 4:
+ if (event.at0 == kMenuEventEnter)
+ {
+ at29[0] = 0;
+ if (strcmp(at20, ""))
+ at37 = 2;
+ else
+ at37 = 3;
+ return false;
+ }
+ return CGameMenuItem::Event(event);
+ case 1:
+ case 2:
+ case 3:
+ switch (event.at0)
+ {
+ case kMenuEventEnter:
+ switch (at37)
+ {
+ case 1:
+ at36 = strcmp(at20,at29) == 0;
+ if (at36)
+ at37 = 0;
+ else
+ at37 = 4;
+ if (!at36)
+ {
+ at5b = totalclock;
+ pMenu->FocusPrevItem();
+ }
+ else
+ {
+ at5f->at20 = 0;
+ at5f->Draw();
+ gbAdultContent = false;
+ // NUKE-TODO:
+ //CONFIG_WriteAdultMode();
+ pMenu->FocusPrevItem();
+ }
+ return false;
+ case 2:
+ at36 = strcmp(at20,at29) == 0;
+ if (at36)
+ at37 = 0;
+ else
+ at37 = 4;
+ if (at36)
+ {
+ strcpy(at20, "");
+ strcpy(gzAdultPassword, "");
+ // NUKE-TODO:
+ //CONFIG_WriteAdultMode();
+ at37 = 0;
+ }
+ else
+ at5b = totalclock;
+ return false;
+ case 3:
+ strcpy(at20, at29);
+ strcpy(at20, gzAdultPassword);
+ strcpy(gzAdultPassword, "");
+ // NUKE-TODO:
+ //CONFIG_WriteAdultMode();
+ at37 = 0;
+ return false;
+ }
+ break;
+ case kMenuEventEscape:
+ at37 = 0;
+ Draw();
+ return false;
+ case kMenuEventKey:
+ if (at32 < 8)
+ {
+ char key = Btoupper(g_keyAsciiTable[event.at2]);
+ if (isalnum(key) || ispunct(key) || isspace(key))
+ {
+ at29[at32++] = key;
+ at29[at32] = 0;
+ }
+ }
+ return false;
+ case kMenuEventBackSpace:
+ if (at32 > 0)
+ at29[--at32] = 0;
+ return false;
+ case kMenuEventLeft:
+ case kMenuEventRight:
+ case kMenuEventSpace:
+ return false;
+ }
+ }
+ return CGameMenuItem::Event(event);
+}
\ No newline at end of file
diff --git a/source/blood/src/gamemenu.h b/source/blood/src/gamemenu.h
new file mode 100644
index 000000000..f9163df24
--- /dev/null
+++ b/source/blood/src/gamemenu.h
@@ -0,0 +1,490 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "compat.h"
+#include "common_game.h"
+#include "blood.h"
+#include "inifile.h"
+#include "resource.h"
+#include "qav.h"
+
+#define M_MOUSETIMEOUT 210
+
+#define kMaxGameMenuItems 128
+#define kMaxGameCycleItems 128
+#define kMaxPicCycleItems 128
+#define kMaxTitleLength 32
+
+// alpha increments of 3 --> 255 / 3 = 85 --> round up to power of 2 --> 128 --> divide by 2 --> 64 alphatabs required
+// use 16 anyway :P
+#define MOUSEUSEALPHA (videoGetRenderMode() != REND_CLASSIC || numalphatabs >= 15)
+#define MOUSEALPHA (MOUSEUSEALPHA ? clamp((totalclock - gGameMenuMgr.m_mouselastactivity - 90)*3, 0, 255) : 0)
+#define CURSORALPHA (MOUSEUSEALPHA ? clamp((totalclock - gGameMenuMgr.m_mouselastactivity - 90)*2 + (255/3), (255/3), 255) : 255/3)
+#define MOUSEACTIVECONDITION (totalclock - gGameMenuMgr.m_mouselastactivity < M_MOUSETIMEOUT)
+#define MOUSEACTIVECONDITIONAL(condition) (MOUSEACTIVECONDITION && (condition))
+#define MOUSEINACTIVECONDITIONAL(condition) (!MOUSEACTIVECONDITION && (condition))
+#define MOUSEWATCHPOINTCONDITIONAL(condition) ((condition) || gGameMenuMgr.m_mousewake_watchpoint || gGameMenuMgr.m_menuchange_watchpoint == 3)
+
+enum {
+ kMenuEventNone = 0,
+ kMenuEventKey = 1,
+ kMenuEventUp = 2,
+ kMenuEventDown = 3,
+ kMenuEventLeft = 4,
+ kMenuEventRight = 5,
+ kMenuEventEnter = 6,
+ kMenuEventEscape = 7,
+ kMenuEventSpace = 8,
+ kMenuEventBackSpace = 9,
+ kMenuEventDelete = 10,
+ kMenuEventScrollUp = 11,
+ kMenuEventScrollDown = 12,
+
+
+ kMenuEventInit = 0x8000,
+ kMenuEventDeInit = 0x8001
+};
+
+enum {
+ kMenuSliderNone = 0,
+ kMenuSliderValue,
+ kMenuSliderPercent,
+ kMenuSliderQ16
+};
+
+struct CGameMenuEvent {
+ unsigned short at0;
+ char at2;
+};
+
+// NUKE-TODO:
+#ifdef DrawText
+#undef DrawText
+#endif
+
+class CMenuTextMgr
+{
+public:
+ int at0;
+ CMenuTextMgr();
+ void DrawText(const char *pString, int nFont, int x, int y, int nShade, int nPalette, bool shadow );
+ void GetFontInfo(int nFont, const char *pString, int *pXSize, int *pYSize);
+};
+
+class CGameMenu;
+
+class CGameMenuItem {
+public:
+ CGameMenu *pMenu;
+ const char* m_pzText;
+ int m_nFont;
+ int m_nX;
+ int m_nY;
+ int m_nWidth;
+ void (*pPreDrawCallback)(CGameMenuItem *pItem);
+ //int nFlags;
+ unsigned int bCanSelect : 1;
+ unsigned int bEnable : 1;
+ unsigned int bNoDraw : 1;
+ CGameMenuItem();
+ virtual ~CGameMenuItem();
+ virtual void Draw(void) = 0;
+ virtual bool Event(CGameMenuEvent &);
+ virtual bool MouseEvent(CGameMenuEvent &);
+};
+
+class CGameMenuItemText : public CGameMenuItem
+{
+public:
+ int at20;
+ CGameMenuItemText();
+ CGameMenuItemText(const char *, int, int, int, int);
+ virtual void Draw(void);
+ //virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemTitle : public CGameMenuItem
+{
+public:
+ int at20;
+ CGameMenuItemTitle();
+ CGameMenuItemTitle(const char *, int, int, int, int);
+ virtual void Draw(void);
+ //virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemZBool : public CGameMenuItem
+{
+public:
+ bool at20;
+ const char *at21;
+ const char *at25;
+ void (*at29)(CGameMenuItemZBool *);
+ CGameMenuItemZBool();
+ CGameMenuItemZBool(const char *,int,int,int,int,bool,void (*)(CGameMenuItemZBool *),const char *,const char *);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemChain : public CGameMenuItem
+{
+public:
+ int at20;
+ CGameMenu *at24;
+ int at28;
+ void(*at2c)(CGameMenuItemChain *);
+ int at30;
+ CGameMenuItemChain();
+ CGameMenuItemChain(const char *, int, int, int, int, int, CGameMenu *, int, void(*)(CGameMenuItemChain *), int);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItem7EA1C : public CGameMenuItem
+{
+public:
+ int at20; // text align
+ CGameMenu *at24;
+ int at28;
+ void(*at2c)(CGameMenuItem7EA1C *);
+ int at30;
+ IniFile *at34;
+ char at38[16];
+ char at48[16];
+ CGameMenuItem7EA1C();
+ CGameMenuItem7EA1C(const char *a1, int a2, int a3, int a4, int a5, const char *a6, const char *a7, int a8, int a9, void(*a10)(CGameMenuItem7EA1C *), int a11);
+ void Setup(void);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItem7EE34 : public CGameMenuItem
+{
+public:
+ int at20;
+ int at24;
+ CGameMenu *at28;
+ CGameMenu *at2c;
+ CGameMenuItem7EE34();
+ CGameMenuItem7EE34(const char *a1, int a2, int a3, int a4, int a5, int a6);
+ void Setup(void);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemChain7F2F0 : public CGameMenuItemChain
+{
+public:
+ int at34;
+ CGameMenuItemChain7F2F0();
+ CGameMenuItemChain7F2F0(char *a1, int a2, int a3, int a4, int a5, int a6, CGameMenu *a7, int a8, void(*a9)(CGameMenuItemChain *), int a10);
+ //virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemBitmap : public CGameMenuItem
+{
+public:
+ int at20;
+ CGameMenuItemBitmap();
+ CGameMenuItemBitmap(const char *, int, int, int, int);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemBitmapLS : public CGameMenuItemBitmap
+{
+public:
+ int at24;
+ int at28;
+ CGameMenuItemBitmapLS();
+ CGameMenuItemBitmapLS(const char *, int, int, int, int);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemKeyList : public CGameMenuItem
+{
+public:
+ void(*pCallback)(CGameMenuItemKeyList *);
+ int at24;
+ int nRows;
+ int nTopDelta;
+ int nFocus;
+ int nGameFuncs;
+ bool bScan;
+ CGameMenuItemKeyList();
+ CGameMenuItemKeyList(const char * a1, int a2, int a3, int a4, int a5, int a6, int a7, void(*a8)(CGameMenuItemKeyList *));
+ void Scan(void);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+ virtual bool MouseEvent(CGameMenuEvent &);
+};
+
+class CGameMenuItemSlider : public CGameMenuItem
+{
+public:
+ int *pValue;
+ int nValue;
+ int nRangeLow;
+ int nRangeHigh;
+ int nStep;
+ void(*pCallback)(CGameMenuItemSlider *);
+ int nSliderTile;
+ int nCursorTile;
+ int nShowValue;
+ CGameMenuItemSlider();
+ CGameMenuItemSlider(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, int _nValue, int _nRangeLow, int _nRangeHigh, int _nStep, void(*_pCallback)(CGameMenuItemSlider *), int _nSliderTile, int _nCursorTile, int _nShowValue = kMenuSliderNone);
+ CGameMenuItemSlider(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, int *pnValue, int _nRangeLow, int _nRangeHigh, int _nStep, void(*_pCallback)(CGameMenuItemSlider *), int _nSliderTile, int _nCursorTile, int _nShowValue = kMenuSliderNone);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+ virtual bool MouseEvent(CGameMenuEvent &);
+};
+
+class CGameMenuItemSliderFloat : public CGameMenuItem
+{
+public:
+ float *pValue;
+ float fValue;
+ float fRangeLow;
+ float fRangeHigh;
+ float fStep;
+ void(*pCallback)(CGameMenuItemSliderFloat *);
+ int nSliderTile;
+ int nCursorTile;
+ int nShowValue;
+ CGameMenuItemSliderFloat();
+ CGameMenuItemSliderFloat(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, float _fValue, float _fRangeLow, float _fRangeHigh, float _fStep, void(*_pCallback)(CGameMenuItemSliderFloat *), int _nSliderTile, int _nCursorTile, int _nShowValue = kMenuSliderNone);
+ CGameMenuItemSliderFloat(const char *_pzText, int _nFont, int _nX, int _nY, int _nWidth, float *pnValue, float _fRangeLow, float _fRangeHigh, float _fStep, void(*_pCallback)(CGameMenuItemSliderFloat *), int _nSliderTile, int _nCursorTile, int _nShowValue = kMenuSliderNone);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemZEdit : public CGameMenuItem
+{
+public:
+ char *at20;
+ int at24;
+ int at28;
+ void(*at2c)(CGameMenuItemZEdit *, CGameMenuEvent *);
+ char at30;
+ char at31;
+ char at32;
+ CGameMenuItemZEdit();
+ CGameMenuItemZEdit(const char *, int, int, int, int, char *, int, char, void(*)(CGameMenuItemZEdit *, CGameMenuEvent *), int);
+ void AddChar(char);
+ void BackChar(void);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemZEditBitmap : public CGameMenuItem
+{
+public:
+ char *at20;
+ int at24;
+ int at28;
+ CGameMenuItemBitmapLS *at2c;
+ void(*at30)(CGameMenuItemZEditBitmap *, CGameMenuEvent *);
+ char at34;
+ char at35;
+ char at36;
+ char at37;
+ CGameMenuItemZEditBitmap();
+ CGameMenuItemZEditBitmap(char *, int, int, int, int, char *, int, char, void(*)(CGameMenuItemZEditBitmap *, CGameMenuEvent *), int);
+ void AddChar(char);
+ void BackChar(void);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemQAV : public CGameMenuItem
+{
+public:
+ const char *at20;
+ DICTNODE *at24;
+ QAV *at28;
+ int at2c;
+ int at30;
+ bool bWideScreen;
+ bool bClearBackground;
+ CGameMenuItemQAV();
+ CGameMenuItemQAV(const char *, int, int, int, const char *, bool widescreen = false, bool clearbackground = false);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+ void Reset(void);
+};
+
+class CGameMenuItemZCycleSelect : public CGameMenuItem
+{
+public:
+ void(*m_pCallback)(CGameMenuItemZCycleSelect *);
+ int m_nRows;
+ int m_nTopDelta;
+ int m_nFocus;
+ int m_nItems;
+ int *m_pReturn;
+ const char **m_pzStrings;
+ CGameMenuItemZCycleSelect();
+ CGameMenuItemZCycleSelect(const char *pzText, int nFont, int nX, int nY, int nWidth, int nRows, int nItems, const char **pzStrings, int *pReturn, void(*pCallback)(CGameMenuItemZCycleSelect *));
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+ virtual bool MouseEvent(CGameMenuEvent &);
+};
+
+
+class CGameMenuItemZCycle : public CGameMenuItem
+{
+public:
+ int m_nItems;
+ int m_nFocus;
+ int m_nAlign;
+ const char *m_pzStrings[kMaxGameCycleItems];
+ char m_zTitle[kMaxTitleLength];
+ void(*m_pCallback)(CGameMenuItemZCycle *);
+ void(*m_pCallbackSelect)(CGameMenuItemZCycleSelect *);
+ bool m_bMenu;
+ int m_nMenuSelectReturn;
+ CGameMenu *m_pMenuSelect;
+ CGameMenuItemTitle *m_pItemSelectTitle;
+ CGameMenuItemZCycleSelect *m_pItemSelect;
+ CGameMenuItemZCycle();
+ CGameMenuItemZCycle(const char *, int, int, int, int, int, void(*)(CGameMenuItemZCycle *), const char **, int, int, bool = false, void(*)(CGameMenuItemZCycleSelect*) = NULL);
+ ~CGameMenuItemZCycle();
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+ void Add(const char *, bool);
+ void Next(void);
+ void Prev(void);
+ void Clear(void);
+ void SetTextArray(const char **, int, int);
+ void SetTextIndex(int);
+};
+
+class CGameMenuItemYesNoQuit : public CGameMenuItem
+{
+public:
+ int at20;
+ int m_nRestart;
+ CGameMenuItemYesNoQuit();
+ CGameMenuItemYesNoQuit(const char *, int, int, int, int, int, int);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+class CGameMenuItemPicCycle : public CGameMenuItem
+{
+public:
+ int m_nItems;
+ int at24;
+ int at28;
+ int at2c;
+ int at30[kMaxPicCycleItems];
+ void(*atb0)(CGameMenuItemPicCycle *);
+ int atb4;
+ CGameMenuItemPicCycle();
+ CGameMenuItemPicCycle(int, int, void(*)(CGameMenuItemPicCycle *), int *, int, int);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+ void Add(int, bool);
+ void Next(void);
+ void Prev(void);
+ void Clear(void);
+ void SetPicArray(int *, int, int);
+ void SetPicIndex(int);
+};
+
+class CGameMenuItemPassword : public CGameMenuItem
+{
+public:
+ char at20[9];
+ char at29[9];
+ int at32;
+ char at36;
+ int at37;
+ char at3b[32];
+ int at5b;
+ CGameMenuItemZBool *at5f;
+ CGameMenuItemPassword();
+ CGameMenuItemPassword(const char *, int, int, int);
+ virtual void Draw(void);
+ virtual bool Event(CGameMenuEvent &);
+};
+
+
+class CGameMenu
+{
+public:
+ int m_nItems;
+ int m_nFocus;
+ int at8;
+ char atc;
+ CGameMenuItem *pItemList[kMaxGameMenuItems]; // atd
+ CGameMenu();
+ CGameMenu(int);
+ ~CGameMenu();
+ void InitializeItems(CGameMenuEvent &event);
+ void Draw(void);
+ bool Event(CGameMenuEvent &event);
+ void Add(CGameMenuItem *pItem, bool active);
+ void SetFocusItem(int nItem);
+ void SetFocusItem(CGameMenuItem *Item);
+ bool CanSelectItem(int nItem);
+ void FocusPrevItem(void);
+ void FocusNextItem(void);
+ bool IsFocusItem(CGameMenuItem *pItem);
+ bool MouseEvent(CGameMenuEvent &event);
+};
+
+class CGameMenuMgr
+{
+public:
+ static bool m_bInitialized;
+ static bool m_bActive;
+ static bool m_bFirstPush;
+ CGameMenu *pTempMenu;
+ CGameMenu *pActiveMenu;
+ CGameMenu *pMenuStack[8];
+ int nMenuPointer;
+ int32_t m_mouselastactivity;
+ int32_t m_mousewake_watchpoint, m_menuchange_watchpoint;
+ int32_t m_mousecaught;
+ vec2_t m_prevmousepos, m_mousepos, m_mousedownpos;
+ bool m_postPop;
+ CGameMenuMgr();
+ ~CGameMenuMgr();
+ void InitializeMenu(void);
+ void DeInitializeMenu(void);
+ bool Push(CGameMenu *pMenu, int data);
+ void Pop(void);
+ void PostPop(void);
+ void Draw(void);
+ void Clear(void);
+ void Process(void);
+ void Deactivate(void);
+ bool MouseOutsideBounds(vec2_t const * const pos, const int32_t x, const int32_t y, const int32_t width, const int32_t height);
+};
+
+extern CMenuTextMgr gMenuTextMgr;
+extern CGameMenuMgr gGameMenuMgr;
diff --git a/source/blood/src/gameutil.cpp b/source/blood/src/gameutil.cpp
new file mode 100644
index 000000000..2c9a50067
--- /dev/null
+++ b/source/blood/src/gameutil.cpp
@@ -0,0 +1,904 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include "build.h"
+#include "common_game.h"
+
+#include "actor.h"
+#include "db.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "tile.h"
+#include "trig.h"
+
+POINT2D baseWall[kMaxWalls];
+POINT3D baseSprite[kMaxSprites];
+int baseFloor[kMaxSectors];
+int baseCeil[kMaxSectors];
+int velFloor[kMaxSectors];
+int velCeil[kMaxSectors];
+short gUpperLink[kMaxSectors];
+short gLowerLink[kMaxSectors];
+HITINFO gHitInfo;
+
+bool AreSectorsNeighbors(int sect1, int sect2)
+{
+ dassert(sect1 >= 0 && sect1 < kMaxSectors);
+ dassert(sect2 >= 0 && sect2 < kMaxSectors);
+ if (sector[sect1].wallnum < sector[sect2].wallnum)
+ {
+ for (int i = 0; i < sector[sect1].wallnum; i++)
+ {
+ if (wall[sector[sect1].wallptr+i].nextsector == sect2)
+ {
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < sector[sect2].wallnum; i++)
+ {
+ if (wall[sector[sect2].wallptr+i].nextsector == sect1)
+ {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+bool FindSector(int nX, int nY, int nZ, int *nSector)
+{
+ int32_t nZFloor, nZCeil;
+ dassert(*nSector >= 0 && *nSector < kMaxSectors);
+ if (inside(nX, nY, *nSector))
+ {
+ getzsofslope(*nSector, nX, nY, &nZCeil, &nZFloor);
+ if (nZ >= nZCeil && nZ <= nZFloor)
+ {
+ return 1;
+ }
+ }
+ walltype *pWall = &wall[sector[*nSector].wallptr];
+ for (int i = sector[*nSector].wallnum; i > 0; i--, pWall++)
+ {
+ int nOSector = pWall->nextsector;
+ if (nOSector >= 0 && inside(nX, nY, nOSector))
+ {
+ getzsofslope(nOSector, nX, nY, &nZCeil, &nZFloor);
+ if (nZ >= nZCeil && nZ <= nZFloor)
+ {
+ *nSector = nOSector;
+ return 1;
+ }
+ }
+ }
+ for (int i = 0; i < numsectors; i++)
+ {
+ if (inside(nX, nY, i))
+ {
+ getzsofslope(i, nX, nY, &nZCeil, &nZFloor);
+ if (nZ >= nZCeil && nZ <= nZFloor)
+ {
+ *nSector = i;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+bool FindSector(int nX, int nY, int *nSector)
+{
+ dassert(*nSector >= 0 && *nSector < kMaxSectors);
+ if (inside(nX, nY, *nSector))
+ {
+ return 1;
+ }
+ walltype *pWall = &wall[sector[*nSector].wallptr];
+ for (int i = sector[*nSector].wallnum; i > 0; i--, pWall++)
+ {
+ int nOSector = pWall->nextsector;
+ if (nOSector >= 0 && inside(nX, nY, nOSector))
+ {
+ *nSector = nOSector;
+ return 1;
+ }
+ }
+ for (int i = 0; i < numsectors; i++)
+ {
+ if (inside(nX, nY, i))
+ {
+ *nSector = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void CalcFrameRate(void)
+{
+ static int ticks[64];
+ static int index;
+ if (ticks[index] != gFrameClock)
+ {
+ gFrameRate = (120*64)/(gFrameClock-ticks[index]);
+ ticks[index] = gFrameClock;
+ }
+ index = (index+1) & 63;
+}
+
+bool CheckProximity(spritetype *pSprite, int nX, int nY, int nZ, int nSector, int nDist)
+{
+ dassert(pSprite != NULL);
+ int oX = klabs(nX-pSprite->x)>>4;
+ if (oX >= nDist) return 0;
+
+ int oY = klabs(nY-pSprite->y)>>4;
+ if (oY >= nDist) return 0;
+
+ int oZ = klabs(nZ-pSprite->z)>>8;
+ if (oZ >= nDist) return 0;
+
+ if (approxDist(oX, oY) >= nDist) return 0;
+
+ int bottom, top;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (cansee(pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, nX, nY, nZ, nSector))
+ return 1;
+ if (cansee(pSprite->x, pSprite->y, bottom, pSprite->sectnum, nX, nY, nZ, nSector))
+ return 1;
+ if (cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, nX, nY, nZ, nSector))
+ return 1;
+ return 0;
+}
+
+bool CheckProximityPoint(int nX1, int nY1, int nZ1, int nX2, int nY2, int nZ2, int nDist)
+{
+ int oX = klabs(nX2-nX1)>>4;
+ if (oX >= nDist)
+ return 0;
+ int oY = klabs(nY2-nY1)>>4;
+ if (oY >= nDist)
+ return 0;
+ int oZ = klabs(nZ2-nZ1)>>4;
+ if (oZ >= nDist)
+ return 0;
+ if (approxDist(oX, oY) >= nDist) return 0;
+ return 1;
+}
+
+bool CheckProximityWall(int nWall, int x, int y, int nDist)
+{
+ int x1 = wall[nWall].x;
+ int y1 = wall[nWall].y;
+ int x2 = wall[wall[nWall].point2].x;
+ int y2 = wall[wall[nWall].point2].y;
+ nDist <<= 4;
+ if (x1 < x2)
+ {
+ if (x <= x1 - nDist || x >= x2 + nDist)
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ if (x <= x2 - nDist || x >= x1 + nDist)
+ {
+ return 0;
+ }
+ if (x1 == x2)
+ {
+ int px1 = x - x1;
+ int py1 = y - y1;
+ int px2 = x - x2;
+ int py2 = y - y2;
+ int dist1 = px1 * px1 + py1 * py1;
+ int dist2 = px2 * px2 + py2 * py2;
+ if (y1 < y2)
+ {
+ if (y <= y1 - nDist || y >= y2 + nDist)
+ {
+ return 0;
+ }
+ if (y < y1)
+ {
+ return dist1 < nDist * nDist;
+ }
+ if (y > y2)
+ {
+ return dist2 < nDist * nDist;
+ }
+ }
+ else
+ {
+ if (y <= y2 - nDist || y >= y1 + nDist)
+ {
+ return 0;
+ }
+ if (y < y2)
+ {
+ return dist2 < nDist * nDist;
+ }
+ if (y > y1)
+ {
+ return dist1 < nDist * nDist;
+ }
+ }
+ return 1;
+ }
+ }
+ if (y1 < y2)
+ {
+ if (y <= y1 - nDist || y >= y2 + nDist)
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ if (y <= y2 - nDist || y >= y1 + nDist)
+ {
+ return 0;
+ }
+ if (y1 == y2)
+ {
+ int px1 = x - x1;
+ int py1 = y - y1;
+ int px2 = x - x2;
+ int py2 = y - y2;
+ int check1 = px1 * px1 + py1 * py1;
+ int check2 = px2 * px2 + py2 * py2;
+ if (x1 < x2)
+ {
+ if (x <= x1 - nDist || x >= x2 + nDist)
+ {
+ return 0;
+ }
+ if (x < x1)
+ {
+ return check1 < nDist * nDist;
+ }
+ if (x > x2)
+ {
+ return check2 < nDist * nDist;
+ }
+ }
+ else
+ {
+ if (x <= x2 - nDist || x >= x1 + nDist)
+ {
+ return 0;
+ }
+ if (x < x2)
+ {
+ return check2 < nDist * nDist;
+ }
+ if (x > x1)
+ {
+ return check1 < nDist * nDist;
+ }
+ }
+ }
+ }
+
+ int dx = x2 - x1;
+ int dy = y2 - y1;
+ int px = x - x2;
+ int py = y - y2;
+ int side = px * dx + dy * py;
+ if (side >= 0)
+ {
+ return px * px + py * py < nDist * nDist;
+ }
+ px = x - x1;
+ py = y - y1;
+ side = px * dx + dy * py;
+ if (side <= 0)
+ {
+ return px * px + py * py < nDist * nDist;
+ }
+ int check1 = px * dy - dx * py;
+ int check2 = dy * dy + dx * dx;
+ return check1 * check1 < check2 * nDist * nDist;
+}
+
+int GetWallAngle(int nWall)
+{
+ int nWall2 = wall[nWall].point2;
+ return getangle(wall[nWall2].x - wall[nWall].x, wall[nWall2].y - wall[nWall].y);
+}
+
+void GetWallNormal(int nWall, int *pX, int *pY)
+{
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ int nWall2 = wall[nWall].point2;
+ int dX = -(wall[nWall2].y - wall[nWall].y);
+ dX >>= 4;
+ int dY = wall[nWall2].x - wall[nWall].x;
+ dY >>= 4;
+ int nLength = ksqrt(dX*dX+dY*dY);
+ if (nLength <= 0)
+ nLength = 1;
+ *pX = divscale16(dX, nLength);
+ *pY = divscale16(dY, nLength);
+}
+
+bool IntersectRay(int wx, int wy, int wdx, int wdy, int x1, int y1, int z1, int x2, int y2, int z2, int *ix, int *iy, int *iz)
+{
+ int dX = x1 - x2;
+ int dY = y1 - y2;
+ int dZ = z1 - z2;
+ int side = wdx * dY - wdy * dX;
+ int dX2 = x1 - wx;
+ int dY2 = y1 - wy;
+ int check1 = dX2 * dY - dY2 * dX;
+ int check2 = wdx * dY2 - wdy * dX2;
+ if (side >= 0)
+ {
+ if (!side)
+ return 0;
+ if (check1 < 0)
+ return 0;
+ if (check2 < 0 || check2 >= side)
+ return 0;
+ }
+ else
+ {
+ if (check1 > 0)
+ return 0;
+ if (check2 > 0 || check2 <= side)
+ return 0;
+ }
+ int nScale = divscale16(check2, side);
+ *ix = x1 + mulscale16(dX, nScale);
+ *iy = y1 + mulscale16(dY, nScale);
+ *iz = z1 + mulscale16(dZ, nScale);
+ return 1;
+}
+
+int HitScan(spritetype *pSprite, int z, int dx, int dy, int dz, unsigned int nMask, int a8)
+{
+ dassert(pSprite != NULL);
+ dassert(dx != 0 || dy != 0);
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int nSector = pSprite->sectnum;
+ int bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~256;
+ if (a8)
+ {
+ hitscangoal.x = x + mulscale30(a8 << 4, Cos(pSprite->ang));
+ hitscangoal.y = y + mulscale30(a8 << 4, Sin(pSprite->ang));
+ }
+ else
+ {
+ hitscangoal.x = hitscangoal.y = 0x1ffffff;
+ }
+ vec3_t pos = { x, y, z };
+ hitdata_t hitData;
+ hitData.pos.z = gHitInfo.hitz;
+ hitscan(&pos, nSector, dx, dy, dz << 4, &hitData, nMask);
+ gHitInfo.hitsect = hitData.sect;
+ gHitInfo.hitwall = hitData.wall;
+ gHitInfo.hitsprite = hitData.sprite;
+ gHitInfo.hitx = hitData.pos.x;
+ gHitInfo.hity = hitData.pos.y;
+ gHitInfo.hitz = hitData.pos.z;
+ hitscangoal.x = hitscangoal.y = 0x1ffffff;
+ pSprite->cstat = bakCstat;
+ if (gHitInfo.hitsprite >= kMaxSprites || gHitInfo.hitwall >= kMaxWalls || gHitInfo.hitsect >= kMaxSectors)
+ return -1;
+ if (gHitInfo.hitsprite >= 0)
+ return 3;
+ if (gHitInfo.hitwall >= 0)
+ {
+ if (wall[gHitInfo.hitwall].nextsector == -1)
+ return 0;
+ int nZCeil, nZFloor;
+ getzsofslope(wall[gHitInfo.hitwall].nextsector, gHitInfo.hitx, gHitInfo.hity, &nZCeil, &nZFloor);
+ if (gHitInfo.hitz <= nZCeil || gHitInfo.hitz >= nZFloor)
+ return 0;
+ return 4;
+ }
+ if (gHitInfo.hitsect >= 0)
+ return 1 + (z < gHitInfo.hitz);
+ return -1;
+}
+
+int VectorScan(spritetype *pSprite, int nOffset, int nZOffset, int dx, int dy, int dz, int nRange, int ac)
+{
+ int nNum = 256;
+ dassert(pSprite != NULL);
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ int x1 = pSprite->x+mulscale30(nOffset, Cos(pSprite->ang+512));
+ int y1 = pSprite->y+mulscale30(nOffset, Sin(pSprite->ang+512));
+ int z1 = pSprite->z+nZOffset;
+ int bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~256;
+ int nSector = pSprite->sectnum;
+ if (nRange)
+ {
+ hitscangoal.x = x1+mulscale30(nRange<<4, Cos(pSprite->ang));
+ hitscangoal.y = y1+mulscale30(nRange<<4, Sin(pSprite->ang));
+ }
+ else
+ {
+ hitscangoal.x = hitscangoal.y = 0x1fffffff;
+ }
+ vec3_t pos = { x1, y1, z1 };
+ hitdata_t hitData;
+ hitData.pos.z = gHitInfo.hitz;
+ hitscan(&pos, nSector, dx, dy, dz << 4, &hitData, CLIPMASK1);
+ gHitInfo.hitsect = hitData.sect;
+ gHitInfo.hitwall = hitData.wall;
+ gHitInfo.hitsprite = hitData.sprite;
+ gHitInfo.hitx = hitData.pos.x;
+ gHitInfo.hity = hitData.pos.y;
+ gHitInfo.hitz = hitData.pos.z;
+ hitscangoal.x = hitscangoal.y = 0x1ffffff;
+ pSprite->cstat = bakCstat;
+ while (nNum--)
+ {
+ if (gHitInfo.hitsprite >= kMaxSprites || gHitInfo.hitwall >= kMaxWalls || gHitInfo.hitsect >= kMaxSectors)
+ return -1;
+ if (nRange && approxDist(gHitInfo.hitx - pSprite->x, gHitInfo.hity - pSprite->y) > nRange)
+ return -1;
+ if (gHitInfo.hitsprite >= 0)
+ {
+ spritetype *pOther = &sprite[gHitInfo.hitsprite];
+ if ((pOther->hitag & 8) && !(ac & 1))
+ return 3;
+ if ((pOther->cstat & 0x30) != 0)
+ return 3;
+ int nPicnum = pOther->picnum;
+ if (tilesiz[nPicnum].x == 0 || tilesiz[nPicnum].y == 0)
+ return 3;
+ int height = (tilesiz[nPicnum].y*pOther->yrepeat)<<2;
+ int otherZ = pOther->z;
+ if (pOther->cstat & 0x80)
+ otherZ += height / 2;
+ int nOffset = picanm[nPicnum].yofs;
+ if (nOffset)
+ otherZ -= (nOffset*pOther->yrepeat)<<2;
+ dassert(height > 0);
+ int height2 = scale(otherZ-gHitInfo.hitz, tilesiz[nPicnum].y, height);
+ if (!(pOther->cstat & 8))
+ height2 = tilesiz[nPicnum].y-height2;
+ if (height2 >= 0 && height2 < tilesiz[nPicnum].y)
+ {
+ int width = (tilesiz[nPicnum].x*pOther->xrepeat)>>2;
+ width = (width*3)/4;
+ int check1 = ((y1 - pOther->y)*dx - (x1 - pOther->x)*dy) / ksqrt(dx*dx+dy*dy);
+ dassert(width > 0);
+ int width2 = scale(check1, tilesiz[nPicnum].x, width);
+ int nOffset = picanm[nPicnum].xofs;
+ width2 += nOffset + tilesiz[nPicnum].x / 2;
+ if (width2 >= 0 && width2 < tilesiz[nPicnum].x)
+ {
+ char *pData = tileLoadTile(nPicnum);
+ if (pData[width2*tilesiz[nPicnum].y+height2] != (char)255)
+ return 3;
+ }
+ }
+ int bakCstat = pOther->cstat;
+ pOther->cstat &= ~256;
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ x1 = gHitInfo.hitx;
+ y1 = gHitInfo.hity;
+ z1 = gHitInfo.hitz;
+ pos = { x1, y1, z1 };
+ hitData.pos.z = gHitInfo.hitz;
+ hitscan(&pos, pOther->sectnum,
+ dx, dy, dz << 4, &hitData, CLIPMASK1);
+ gHitInfo.hitsect = hitData.sect;
+ gHitInfo.hitwall = hitData.wall;
+ gHitInfo.hitsprite = hitData.sprite;
+ gHitInfo.hitx = hitData.pos.x;
+ gHitInfo.hity = hitData.pos.y;
+ gHitInfo.hitz = hitData.pos.z;
+ pOther->cstat = bakCstat;
+ continue;
+ }
+ if (gHitInfo.hitwall >= 0)
+ {
+ walltype *pWall = &wall[gHitInfo.hitwall];
+ if (pWall->nextsector == -1)
+ return 0;
+ sectortype *pSector = §or[gHitInfo.hitsect];
+ sectortype *pSectorNext = §or[pWall->nextsector];
+ int nZCeil, nZFloor;
+ getzsofslope(pWall->nextsector, gHitInfo.hitx, gHitInfo.hity, &nZCeil, &nZFloor);
+ if (gHitInfo.hitz <= nZCeil)
+ return 0;
+ if (gHitInfo.hitz >= nZFloor)
+ {
+ if (!(pSector->floorstat&1) || !(pSectorNext->floorstat&1))
+ return 0;
+ return 2;
+ }
+ if (!(pWall->cstat & 0x30))
+ return 0;
+ int nOffset;
+ if (pWall->cstat & 4)
+ nOffset = ClipHigh(pSector->floorz, pSectorNext->floorz);
+ else
+ nOffset = ClipLow(pSector->ceilingz, pSectorNext->ceilingz);
+ nOffset = (gHitInfo.hitz - nOffset) >> 8;
+ if (pWall->cstat & 256)
+ nOffset = -nOffset;
+
+ int nPicnum = pWall->overpicnum;
+ int nSizX = tilesiz[nPicnum].x;
+ int nSizY = tilesiz[nPicnum].y;
+ if (!nSizX || !nSizY)
+ return 0;
+
+ int potX = nSizX == (1<<(picsiz[nPicnum]&15));
+ int potY = nSizY == (1<<(picsiz[nPicnum]>>4));
+
+ nOffset = (nOffset*pWall->yrepeat) / 8;
+ nOffset += (nSizY*pWall->ypanning) / 256;
+ int nLength = approxDist(pWall->x - wall[pWall->point2].x, pWall->y - wall[pWall->point2].y);
+ int nHOffset;
+ if (pWall->cstat & 8)
+ nHOffset = approxDist(gHitInfo.hitx - wall[pWall->point2].x, gHitInfo.hity - wall[pWall->point2].y);
+ else
+ nHOffset = approxDist(gHitInfo.hitx - pWall->x, gHitInfo.hity - pWall->y);
+
+ nHOffset = pWall->xpanning + ((nHOffset*pWall->xrepeat) << 3) / nLength;
+ if (potX)
+ nHOffset &= nSizX - 1;
+ else
+ nHOffset %= nSizX;
+ if (potY)
+ nOffset &= nSizY - 1;
+ else
+ nOffset %= nSizY;
+ char *pData = tileLoadTile(nPicnum);
+ int nPixel;
+ if (potY)
+ nPixel = (nHOffset<<(picsiz[nPicnum]>>4)) + nOffset;
+ else
+ nPixel = nHOffset*nSizY + nOffset;
+
+ if (pData[nPixel] == (char)255)
+ {
+ int bakCstat = pWall->cstat;
+ pWall->cstat &= ~64;
+ int bakCstat2 = wall[pWall->nextwall].cstat;
+ wall[pWall->nextwall].cstat &= ~64;
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ x1 = gHitInfo.hitx;
+ y1 = gHitInfo.hity;
+ z1 = gHitInfo.hitz;
+ pos = { x1, y1, z1 };
+ hitData.pos.z = gHitInfo.hitz;
+ hitscan(&pos, pWall->nextsector,
+ dx, dy, dz << 4, &hitData, CLIPMASK1);
+ gHitInfo.hitsect = hitData.sect;
+ gHitInfo.hitwall = hitData.wall;
+ gHitInfo.hitsprite = hitData.sprite;
+ gHitInfo.hitx = hitData.pos.x;
+ gHitInfo.hity = hitData.pos.y;
+ gHitInfo.hitz = hitData.pos.z;
+ pWall->cstat = bakCstat;
+ wall[pWall->nextwall].cstat = bakCstat2;
+ continue;
+ }
+ return 4;
+ }
+ if (gHitInfo.hitsect >= 0)
+ {
+ if (dz > 0)
+ {
+ if (gUpperLink[gHitInfo.hitsect] < 0)
+ return 2;
+ int nSprite = gUpperLink[gHitInfo.hitsect];
+ int nLink = sprite[nSprite].owner & 0xfff;
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ x1 = gHitInfo.hitx + sprite[nLink].x - sprite[nSprite].x;
+ y1 = gHitInfo.hity + sprite[nLink].y - sprite[nSprite].y;
+ z1 = gHitInfo.hitz + sprite[nLink].z - sprite[nSprite].z;
+ pos = { x1, y1, z1 };
+ hitData.pos.z = gHitInfo.hitz;
+ hitscan(&pos, sprite[nLink].sectnum, dx, dy, dz<<4, &hitData, CLIPMASK1);
+ gHitInfo.hitsect = hitData.sect;
+ gHitInfo.hitwall = hitData.wall;
+ gHitInfo.hitsprite = hitData.sprite;
+ gHitInfo.hitx = hitData.pos.x;
+ gHitInfo.hity = hitData.pos.y;
+ gHitInfo.hitz = hitData.pos.z;
+ continue;
+ }
+ else
+ {
+ if (gLowerLink[gHitInfo.hitsect] < 0)
+ return 1;
+ int nSprite = gLowerLink[gHitInfo.hitsect];
+ int nLink = sprite[nSprite].owner & 0xfff;
+ gHitInfo.hitsect = -1;
+ gHitInfo.hitwall = -1;
+ gHitInfo.hitsprite = -1;
+ x1 = gHitInfo.hitx + sprite[nLink].x - sprite[nSprite].x;
+ y1 = gHitInfo.hity + sprite[nLink].y - sprite[nSprite].y;
+ z1 = gHitInfo.hitz + sprite[nLink].z - sprite[nSprite].z;
+ pos = { x1, y1, z1 };
+ hitData.pos.z = gHitInfo.hitz;
+ hitscan(&pos, sprite[nLink].sectnum, dx, dy, dz<<4, &hitData, CLIPMASK1);
+ gHitInfo.hitsect = hitData.sect;
+ gHitInfo.hitwall = hitData.wall;
+ gHitInfo.hitsprite = hitData.sprite;
+ gHitInfo.hitx = hitData.pos.x;
+ gHitInfo.hity = hitData.pos.y;
+ gHitInfo.hitz = hitData.pos.z;
+ continue;
+ }
+ }
+ return -1;
+ }
+ return -1;
+}
+
+void GetZRange(spritetype *pSprite, int *ceilZ, int *ceilHit, int *floorZ, int *floorHit, int nDist, unsigned int nMask)
+{
+ dassert(pSprite != NULL);
+ int bakCstat = pSprite->cstat;
+ int32_t nTemp1, nTemp2;
+ pSprite->cstat &= ~257;
+ getzrange_old(pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum, (int32_t*)ceilZ, (int32_t*)ceilHit, (int32_t*)floorZ, (int32_t*)floorHit, nDist, nMask);
+ if (((*floorHit) & 0xe000) == 0x4000)
+ {
+ int nSector = (*floorHit) & 0x1fff;
+ if ((nMask & 0x2000) == 0 && (sector[nSector].floorstat & 1))
+ *floorZ = 0x7fffffff;
+ if (sector[nSector].extra > 0)
+ {
+ XSECTOR *pXSector = &xsector[sector[nSector].extra];
+ *floorZ += pXSector->Depth << 10;
+ }
+ if (gUpperLink[nSector] >= 0)
+ {
+ int nSprite = gUpperLink[nSector];
+ int nLink = sprite[nSprite].owner & 0xfff;
+ getzrange_old(pSprite->x+sprite[nLink].x-sprite[nSprite].x, pSprite->y+sprite[nLink].y-sprite[nSprite].y,
+ pSprite->z+sprite[nLink].z-sprite[nSprite].z, sprite[nLink].sectnum, &nTemp1, &nTemp2, (int32_t*)floorZ, (int32_t*)floorHit,
+ nDist, nMask);
+ *floorZ -= sprite[nLink].z - sprite[nSprite].z;
+ }
+ }
+ if (((*ceilHit) & 0xe000) == 0x4000)
+ {
+ int nSector = (*ceilHit) & 0x1fff;
+ if ((nMask & 0x1000) == 0 && (sector[nSector].ceilingstat & 1))
+ *ceilZ = 0x80000000;
+ if (gLowerLink[nSector] >= 0)
+ {
+ int nSprite = gLowerLink[nSector];
+ int nLink = sprite[nSprite].owner & 0xfff;
+ getzrange_old(pSprite->x+sprite[nLink].x-sprite[nSprite].x, pSprite->y+sprite[nLink].y-sprite[nSprite].y,
+ pSprite->z+sprite[nLink].z-sprite[nSprite].z, sprite[nLink].sectnum, (int32_t*)ceilZ, (int32_t*)ceilHit, &nTemp1, &nTemp2,
+ nDist, nMask);
+ *ceilZ -= sprite[nLink].z - sprite[nSprite].z;
+ }
+ }
+ pSprite->cstat = bakCstat;
+}
+
+void GetZRangeAtXYZ(int x, int y, int z, int nSector, int *ceilZ, int *ceilHit, int *floorZ, int *floorHit, int nDist, unsigned int nMask)
+{
+ int32_t nTemp1, nTemp2;
+ getzrange_old(x, y, z, nSector, (int32_t*)ceilZ, (int32_t*)ceilHit, (int32_t*)floorZ, (int32_t*)floorHit, nDist, nMask);
+ if (((*floorHit) & 0xe000) == 0x4000)
+ {
+ int nSector = (*floorHit) & 0x1fff;
+ if ((nMask & 0x2000) == 0 && (sector[nSector].floorstat & 1))
+ *floorZ = 0x7fffffff;
+ if (sector[nSector].extra > 0)
+ {
+ XSECTOR *pXSector = &xsector[sector[nSector].extra];
+ *floorZ += pXSector->Depth << 10;
+ }
+ if (gUpperLink[nSector] >= 0)
+ {
+ int nSprite = gUpperLink[nSector];
+ int nLink = sprite[nSprite].owner & 0xfff;
+ getzrange_old(x+sprite[nLink].x-sprite[nSprite].x, y+sprite[nLink].y-sprite[nSprite].y,
+ z+sprite[nLink].z-sprite[nSprite].z, sprite[nLink].sectnum, &nTemp1, &nTemp2, (int32_t*)floorZ, (int32_t*)floorHit,
+ nDist, nMask);
+ *floorZ -= sprite[nLink].z - sprite[nSprite].z;
+ }
+ }
+ if (((*ceilHit) & 0xe000) == 0x4000)
+ {
+ int nSector = (*ceilHit) & 0x1fff;
+ if ((nMask & 0x1000) == 0 && (sector[nSector].ceilingstat & 1))
+ *ceilZ = 0x80000000;
+ if (gLowerLink[nSector] >= 0)
+ {
+ int nSprite = gLowerLink[nSector];
+ int nLink = sprite[nSprite].owner & 0xfff;
+ getzrange_old(x+sprite[nLink].x-sprite[nSprite].x, y+sprite[nLink].y-sprite[nSprite].y,
+ z+sprite[nLink].z-sprite[nSprite].z, sprite[nLink].sectnum, (int32_t*)ceilZ, (int32_t*)ceilHit, &nTemp1, &nTemp2,
+ nDist, nMask);
+ *ceilZ -= sprite[nLink].z - sprite[nSprite].z;
+ }
+ }
+}
+
+int GetDistToLine(int x1, int y1, int x2, int y2, int x3, int y3)
+{
+ int check = (y1-y3)*(x3-x2);
+ int check2 = (x1-x2)*(y3-y2);
+ if (check2 > check)
+ return -1;
+ int v8 = dmulscale(x1-x2,x3-x2,y1-y3,y3-y2,4);
+ int vv = dmulscale(x3-x2,x3-x2,y3-y2,y3-y2,4);
+ int t1, t2;
+ if (v8 <= 0)
+ {
+ t1 = x2;
+ t2 = y2;
+ }
+ else if (vv > v8)
+ {
+ t1 = x2+scale(x3-x2,v8,vv);
+ t2 = y2+scale(y3-y2,v8,vv);
+ }
+ else
+ {
+ t1 = x3;
+ t2 = y3;
+ }
+ return approxDist(t1-x1, t2-y1);
+}
+
+unsigned int ClipMove(int *x, int *y, int *z, int *nSector, int xv, int yv, int wd, int cd, int fd, unsigned int nMask)
+{
+ int bakX = *x;
+ int bakY = *y;
+ int bakZ = *z;
+ short bakSect = *nSector;
+ unsigned int nRes = clipmove_old((int32_t*)x, (int32_t*)y, (int32_t*)z, &bakSect, xv<<14, yv<<14, wd, cd, fd, nMask);
+ if (bakSect == -1)
+ {
+ *x = bakX; *y = bakY; *z = bakZ;
+ }
+ else
+ {
+ *nSector = bakSect;
+ }
+ return nRes;
+}
+
+int GetClosestSectors(int nSector, int x, int y, int nDist, short *pSectors, char *pSectBit)
+{
+ char sectbits[(kMaxSectors+7)>>3];
+ dassert(pSectors != NULL);
+ memset(sectbits, 0, sizeof(sectbits));
+ pSectors[0] = nSector;
+ SetBitString(sectbits, nSector);
+ int n = 1;
+ int i = 0;
+ if (pSectBit)
+ {
+ memset(pSectBit, 0, (kMaxSectors+7)>>3);
+ SetBitString(pSectBit, nSector);
+ }
+ while (i < n)
+ {
+ int nCurSector = pSectors[i];
+ int nStartWall = sector[nCurSector].wallptr;
+ int nEndWall = nStartWall + sector[nCurSector].wallnum;
+ walltype *pWall = &wall[nStartWall];
+ for (int j = nStartWall; j < nEndWall; j++, pWall++)
+ {
+ int nNextSector = pWall->nextsector;
+ if (nNextSector < 0)
+ continue;
+ if (TestBitString(sectbits, nNextSector))
+ continue;
+ SetBitString(sectbits, nNextSector);
+ int dx = klabs(wall[pWall->point2].x - x)>>4;
+ int dy = klabs(wall[pWall->point2].y - y)>>4;
+ if (dx < nDist && dy < nDist)
+ {
+ if (approxDist(dx, dy) < nDist)
+ {
+ if (pSectBit)
+ SetBitString(pSectBit, nNextSector);
+ pSectors[n++] = nNextSector;
+ }
+ }
+ }
+ i++;
+ }
+ pSectors[n] = -1;
+ return n;
+}
+
+int GetClosestSpriteSectors(int nSector, int x, int y, int nDist, short *pSectors, char *pSectBit, short *a8)
+{
+ char sectbits[(kMaxSectors+7)>>3];
+ dassert(pSectors != NULL);
+ memset(sectbits, 0, sizeof(sectbits));
+ pSectors[0] = nSector;
+ SetBitString(sectbits, nSector);
+ int n = 1, m = 0;
+ int i = 0;
+ if (pSectBit)
+ {
+ memset(pSectBit, 0, (kMaxSectors+7)>>3);
+ SetBitString(pSectBit, nSector);
+ }
+ while (i < n)
+ {
+ int nCurSector = pSectors[i];
+ int nStartWall = sector[nCurSector].wallptr;
+ int nEndWall = nStartWall + sector[nCurSector].wallnum;
+ walltype *pWall = &wall[nStartWall];
+ for (int j = nStartWall; j < nEndWall; j++, pWall++)
+ {
+ int nNextSector = pWall->nextsector;
+ if (nNextSector < 0)
+ continue;
+ if (TestBitString(sectbits, nNextSector))
+ continue;
+ SetBitString(sectbits, nNextSector);
+ if (CheckProximityWall(wall[j].point2, x, y, nDist))
+ {
+ if (pSectBit)
+ SetBitString(pSectBit, nNextSector);
+ pSectors[n++] = nNextSector;
+ if (a8 && pWall->extra > 0)
+ {
+ XWALL *pXWall = &xwall[pWall->extra];
+ if (pXWall->triggerVector && !pXWall->isTriggered && !pXWall->state)
+ a8[m++] = j;
+ }
+ }
+ }
+ i++;
+ }
+ pSectors[n] = -1;
+ if (a8)
+ {
+ a8[m] = -1;
+ }
+ return n;
+}
diff --git a/source/blood/src/gameutil.h b/source/blood/src/gameutil.h
new file mode 100644
index 000000000..2b104582d
--- /dev/null
+++ b/source/blood/src/gameutil.h
@@ -0,0 +1,63 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common_game.h"
+
+struct HITINFO {
+ short hitsect;
+ short hitwall;
+ short hitsprite;
+ int hitx;
+ int hity;
+ int hitz;
+};
+
+extern POINT2D baseWall[kMaxWalls];
+extern POINT3D baseSprite[kMaxSprites];
+extern int baseFloor[kMaxSectors];
+extern int baseCeil[kMaxSectors];
+extern int velFloor[kMaxSectors];
+extern int velCeil[kMaxSectors];
+extern short gUpperLink[kMaxSectors];
+extern short gLowerLink[kMaxSectors];
+extern HITINFO gHitInfo;
+
+bool AreSectorsNeighbors(int sect1, int sect2);
+bool FindSector(int nX, int nY, int nZ, int *nSector);
+bool FindSector(int nX, int nY, int *nSector);
+void CalcFrameRate(void);
+bool CheckProximity(spritetype *pSprite, int nX, int nY, int nZ, int nSector, int nDist);
+bool CheckProximityPoint(int nX1, int nY1, int nZ1, int nX2, int nY2, int nZ2, int nDist);
+bool CheckProximityWall(int nWall, int x, int y, int nDist);
+int GetWallAngle(int nWall);
+void GetWallNormal(int nWall, int *pX, int *pY);
+bool IntersectRay(int wx, int wy, int wdx, int wdy, int x1, int y1, int z1, int x2, int y2, int z2, int *ix, int *iy, int *iz);
+int HitScan(spritetype *pSprite, int z, int dx, int dy, int dz, unsigned int nMask, int a8);
+int VectorScan(spritetype *pSprite, int nOffset, int nZOffset, int dx, int dy, int dz, int nRange, int ac);
+void GetZRange(spritetype *pSprite, int *ceilZ, int *ceilHit, int *floorZ, int *floorHit, int nDist, unsigned int nMask);
+void GetZRangeAtXYZ(int x, int y, int z, int nSector, int *ceilZ, int *ceilHit, int *floorZ, int *floorHit, int nDist, unsigned int nMask);
+int GetDistToLine(int x1, int y1, int x2, int y2, int x3, int y3);
+unsigned int ClipMove(int *x, int *y, int *z, int *nSector, int xv, int yv, int wd, int cd, int fd, unsigned int nMask);
+int GetClosestSectors(int nSector, int x, int y, int nDist, short *pSectors, char *pSectBit);
+int GetClosestSpriteSectors(int nSector, int x, int y, int nDist, short *pSectors, char *pSectBit, short *a8);
diff --git a/source/blood/src/getopt.cpp b/source/blood/src/getopt.cpp
new file mode 100644
index 000000000..aed9da806
--- /dev/null
+++ b/source/blood/src/getopt.cpp
@@ -0,0 +1,92 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include "compat.h"
+#include "getopt.h"
+
+int margc;
+char const * const *margv;
+
+const char *OptArgv[16];
+int OptArgc;
+const char *OptFull;
+const char *SwitchChars = "-/";
+
+int GetOptions(SWITCH *switches)
+{
+ static const char *pChar = NULL;
+ static int OptIndex = 1;
+ if (!pChar)
+ {
+ if (OptIndex >= margc)
+ return -1;
+ pChar = margv[OptIndex++];
+ if (!pChar)
+ return -1;
+ }
+ OptFull = pChar;
+ if (!strchr(SwitchChars, *pChar))
+ {
+ pChar = NULL;
+ return -2;
+ }
+ pChar++;
+ int i;
+ int vd;
+ for (i = 0; true; i++)
+ {
+ if (!switches[i].name)
+ return -3;
+ int nLength = strlen(switches[i].name);
+ if (!Bstrncasecmp(pChar, switches[i].name, nLength) && (pChar[nLength]=='=' || pChar[nLength]==0))
+ {
+ pChar += nLength;
+ if (*pChar=='=')
+ {
+ pChar++;
+ }
+ else
+ {
+ pChar = NULL;
+ }
+ break;
+ }
+ }
+ vd = switches[i].at4;
+ OptArgc = 0;
+ while (OptArgc < switches[i].at8)
+ {
+ if (!pChar)
+ {
+ if (OptIndex >= margc)
+ break;
+ pChar = margv[OptIndex++];
+ if (strchr(SwitchChars, *pChar) != 0)
+ break;
+ }
+ OptArgv[OptArgc++] = pChar;
+ pChar = NULL;
+ }
+ return vd;
+}
diff --git a/source/blood/src/getopt.h b/source/blood/src/getopt.h
new file mode 100644
index 000000000..d0bb6e7e1
--- /dev/null
+++ b/source/blood/src/getopt.h
@@ -0,0 +1,37 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+
+extern int margc;
+extern char const * const *margv;
+
+extern const char *OptArgv[16];
+extern int OptArgc;
+extern const char *OptFull;
+
+struct SWITCH {
+ const char *name;
+ int at4, at8;
+};
+int GetOptions(SWITCH *switches);
\ No newline at end of file
diff --git a/source/blood/src/gib.cpp b/source/blood/src/gib.cpp
new file mode 100644
index 000000000..58cfa0a4d
--- /dev/null
+++ b/source/blood/src/gib.cpp
@@ -0,0 +1,509 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "common_game.h"
+#include "actor.h"
+#include "blood.h"
+#include "db.h"
+#include "callback.h"
+#include "config.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gib.h"
+#include "levels.h"
+#include "sfx.h"
+#include "trig.h"
+
+struct GIBFX
+{
+ FX_ID at0;
+ int at1;
+ int chance;
+ int at9;
+ int atd;
+ int at11;
+};
+
+
+struct GIBTHING
+{
+ int at0;
+ int at4;
+ int chance;
+ int atc;
+ int at10;
+};
+
+struct GIBLIST
+{
+ GIBFX *at0;
+ int at4;
+ GIBTHING *at8;
+ int atc;
+ int at10;
+};
+
+GIBFX gibFxGlassT[] = {
+ { FX_18, 0, 65536, 3, 200, 400 },
+ { FX_31, 0, 32768, 5, 200, 400 }
+};
+
+GIBFX gibFxGlassS[] = {
+ { FX_18, 0, 65536, 8, 200, 400 }
+};
+
+GIBFX gibFxBurnShard[] = {
+ { FX_16, 0, 65536, 12, 500, 1000 }
+};
+
+GIBFX gibFxWoodShard[] = {
+ { FX_17, 0, 65536, 12, 500, 1000 }
+};
+
+GIBFX gibFxMetalShard[] = {
+ { FX_30, 0, 65536, 12, 500, 1000 }
+};
+
+GIBFX gibFxFireSpark[] = {
+ { FX_14, 0, 65536, 8, 500, 1000 }
+};
+
+GIBFX gibFxShockSpark[] = {
+ { FX_15, 0, 65536, 8, 500, 1000 }
+};
+
+GIBFX gibFxBloodChunks[] = {
+ { FX_13, 0, 65536, 8, 90, 600 }
+};
+
+GIBFX gibFxBubblesS[] = {
+ { FX_25, 0, 65536, 8, 200, 400 }
+};
+
+GIBFX gibFxBubblesM[] = {
+ { FX_24, 0, 65536, 8, 200, 400 }
+};
+
+GIBFX gibFxBubblesL[] = {
+ { FX_23, 0, 65536, 8, 200, 400 }
+};
+
+GIBFX gibFxIcicles[] = {
+ { FX_31, 0, 65536, 15, 200, 400 }
+};
+
+GIBFX gibFxGlassCombo1[] = {
+ { FX_18, 0, 65536, 15, 200, 400 },
+ { FX_31, 0, 65536, 10, 200, 400 }
+};
+
+GIBFX gibFxGlassCombo2[] = {
+ { FX_18, 0, 65536, 5, 200, 400 },
+ { FX_20, 0, 53248, 5, 200, 400 },
+ { FX_21, 0, 53248, 5, 200, 400 },
+ { FX_19, 0, 53248, 5, 200, 400 },
+ { FX_22, 0, 53248, 5, 200, 400 }
+};
+
+GIBFX gibFxWoodCombo[] = {
+ { FX_16, 0, 65536, 8, 500, 1000 },
+ { FX_17, 0, 65536, 8, 500, 1000 },
+ { FX_14, 0, 65536, 8, 500, 1000 }
+};
+
+GIBFX gibFxMedicCombo[] = {
+ { FX_18, 0, 32768, 7, 200, 400 },
+ { FX_30, 0, 65536, 7, 500, 1000 },
+ { FX_13, 0, 65536, 10, 90, 600 },
+ { FX_14, 0, 32768, 7, 500, 1000 }
+};
+
+GIBFX gibFxFlareSpark[] = {
+ { FX_28, 0, 32768, 15, 128, -128 }
+};
+
+GIBFX gibFxBloodBits[] = {
+ { FX_13, 0, 45056, 8, 90, 600 }
+};
+
+GIBFX gibFxRockShards[] = {
+ { FX_46, 0, 65536, 10, 300, 800 },
+ { FX_31, 0, 32768, 10, 200, 1000 }
+};
+
+GIBFX gibFxPaperCombo1[] = {
+ { FX_47, 0, 65536, 12, 300, 600 },
+ { FX_14, 0, 65536, 8, 500, 1000 }
+};
+
+GIBFX gibFxPlantCombo1[] = {
+ { FX_44, 0, 45056, 8, 400, 800 },
+ { FX_45, 0, 45056, 8, 300, 800 },
+ { FX_14, 0, 45056, 6, 500, 1000 }
+};
+
+GIBFX gibFx13BBA8[] = {
+ { FX_49, 0, 65536, 4, 80, 300 }
+};
+
+GIBFX gibFx13BBC0[] = {
+ { FX_50, 0, 65536, 4, 80, 0 }
+};
+
+GIBFX gibFx13BBD8[] = {
+ { FX_50, 0, 65536, 20, 800, -40 },
+ { FX_15, 0, 65536, 15, 400, 10 }
+};
+
+GIBFX gibFx13BC04[] = {
+ { FX_32, 0, 65536, 8, 100, 0 }
+};
+
+GIBFX gibFx13BC1C[] = {
+ { FX_56, 0, 65536, 8, 100, 0 }
+};
+
+GIBTHING gibHuman[] = {
+ { 425, 1454, 917504, 300, 900 },
+ { 425, 1454, 917504, 300, 900 },
+ { 425, 1267, 917504, 300, 900 },
+ { 425, 1267, 917504, 300, 900 },
+ { 425, 1268, 917504, 300, 900 },
+ { 425, 1269, 917504, 300, 900 },
+ { 425, 1456, 917504, 300, 900 }
+};
+
+GIBTHING gibMime[] = {
+ { 425, 2405, 917504, 300, 900 },
+ { 425, 2405, 917504, 300, 900 },
+ { 425, 2404, 917504, 300, 900 },
+ { 425, 1268, 32768, 300, 900 },
+ { 425, 1269, 32768, 300, 900 },
+ { 425, 1456, 32768, 300, 900 },
+};
+
+GIBTHING gibHound[] = {
+ { 425, 1326, 917504, 300, 900 },
+ { 425, 1268, 32768, 300, 900 },
+ { 425, 1269, 32768, 300, 900 },
+ { 425, 1456, 32768, 300, 900 }
+};
+
+GIBTHING gibFleshGargoyle[] = {
+ { 425, 1369, 917504, 300, 900 },
+ { 425, 1361, 917504, 300, 900 },
+ { 425, 1268, 32768, 300, 900 },
+ { 425, 1269, 32768, 300, 900 },
+ { 425, 1456, 32768, 300, 900 }
+};
+
+GIBTHING gibAxeZombieHead[] = {
+ { 427, 3405, 917504, 0, 0 }
+};
+
+GIBLIST gibList[] = {
+ { gibFxGlassT, 2, NULL, 0, 300 },
+ { gibFxGlassS, 1, NULL, 0, 300 },
+ { gibFxBurnShard, 1, NULL, 0, 0 },
+ { gibFxWoodShard, 1, NULL, 0, 0 },
+ { gibFxMetalShard, 1, NULL, 0, 0 },
+ { gibFxFireSpark, 1, NULL, 0, 0 },
+ { gibFxShockSpark, 1, NULL, 0, 0 },
+ { gibFxBloodChunks, 1, NULL, 0, 0 },
+ { gibFxBubblesS, 1, NULL, 0, 0 },
+ { gibFxBubblesM, 1, NULL, 0, 0 },
+ { gibFxBubblesL, 1, NULL, 0, 0 },
+ { gibFxIcicles, 1, NULL, 0, 0 },
+ { gibFxGlassCombo1, 2, NULL, 0, 300 },
+ { gibFxGlassCombo2, 5, NULL, 0, 300 },
+ { gibFxWoodCombo, 3, NULL, 0, 0 },
+ { NULL, 0, gibHuman, 7, 0 },
+ { gibFxMedicCombo, 4, NULL, 0, 0 },
+ { gibFxFlareSpark, 1, NULL, 0, 0 },
+ { gibFxBloodBits, 1, NULL, 0, 0 },
+ { gibFxRockShards, 2, NULL, 0, 0 },
+ { gibFxPaperCombo1, 2, NULL, 0, 0 },
+ { gibFxPlantCombo1, 3, NULL, 0, 0 },
+ { gibFx13BBA8, 1, NULL, 0, 0 },
+ { gibFx13BBC0, 1, NULL, 0, 0 },
+ { gibFx13BBD8, 2, NULL, 0, 0 },
+ { gibFx13BC04, 1, NULL, 0, 0 },
+ { gibFx13BC1C, 1, NULL, 0, 0 },
+ { NULL, 0, gibAxeZombieHead, 1, 0 },
+ { NULL, 0, gibMime, 6, 0 },
+ { NULL, 0, gibHound, 4, 0 },
+ { NULL, 0, gibFleshGargoyle, 5, 0 },
+};
+
+void gibCalcWallArea(int a1, int &a2, int &a3, int &a4, int &a5, int &a6, int &a7, int &a8)
+{
+ walltype *pWall = &wall[a1];
+ a2 = (pWall->x+wall[pWall->point2].x)>>1;
+ a3 = (pWall->y+wall[pWall->point2].y)>>1;
+ int nSector = sectorofwall(a1);
+ int32_t ceilZ, floorZ;
+ getzsofslope(nSector, a2, a3, &ceilZ, &floorZ);
+ int32_t ceilZ2, floorZ2;
+ getzsofslope(pWall->nextsector, a2, a3, &ceilZ2, &floorZ2);
+ ceilZ = ClipLow(ceilZ, ceilZ2);
+ floorZ = ClipHigh(floorZ, floorZ2);
+ a7 = floorZ-ceilZ;
+ a5 = wall[pWall->point2].x-pWall->x;
+ a6 = wall[pWall->point2].y-pWall->y;
+ a8 = (a7>>8)*approxDist(a5>>4, a6>>4);
+ a4 = (ceilZ+floorZ)>>1;
+}
+
+int ChanceToCount(int a1, int a2)
+{
+ int vb = a2;
+ if (a1 < 0x10000)
+ {
+ for (int i = 0; i < a2; i++)
+ if (!Chance(a1))
+ vb--;
+ }
+ return vb;
+}
+
+void GibFX(spritetype *pSprite, GIBFX *pGFX, CGibPosition *pPos, CGibVelocity *pVel)
+{
+ int nSector = pSprite->sectnum;
+ if (gbAdultContent && gGameOptions.nGameType == 0 && pGFX->at0 == FX_13)
+ return;
+ CGibPosition gPos(pSprite->x, pSprite->y, pSprite->z);
+ if (pPos)
+ gPos = *pPos;
+ int32_t ceilZ, floorZ;
+ getzsofslope(nSector, gPos.x, gPos.y, &ceilZ, &floorZ);
+ int nCount = ChanceToCount(pGFX->chance, pGFX->at9);
+ int dz1 = floorZ-gPos.z;
+ int dz2 = gPos.z-ceilZ;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ for (int i = 0; i < nCount; i++)
+ {
+ if (!pPos && (pSprite->cstat&48) == 0)
+ {
+ int nAngle = Random(2048);
+ gPos.x = pSprite->x+mulscale30(pSprite->clipdist<<2, Cos(nAngle));
+ gPos.y = pSprite->y+mulscale30(pSprite->clipdist<<2, Sin(nAngle));
+ gPos.z = bottom-Random(bottom-top);
+ }
+ spritetype *pFX = gFX.fxSpawn(pGFX->at0, nSector, gPos.x, gPos.y, gPos.z, 0);
+ if (pFX)
+ {
+ if (pGFX->at1 < 0)
+ pFX->pal = pSprite->pal;
+ if (pVel)
+ {
+ xvel[pFX->index] = pVel->vx+Random2(pGFX->atd);
+ yvel[pFX->index] = pVel->vy+Random2(pGFX->atd);
+ zvel[pFX->index] = pVel->vz-Random(pGFX->at11);
+ }
+ else
+ {
+ xvel[pFX->index] = Random2((pGFX->atd<<18)/120);
+ yvel[pFX->index] = Random2((pGFX->atd<<18)/120);
+ switch(pSprite->cstat&48)
+ {
+ case 16:
+ zvel[pFX->index] = Random2((pGFX->at11<<18)/120);
+ break;
+ default:
+ if (dz2 < dz1 && dz2 < 0x4000)
+ {
+ zvel[pFX->index] = 0;
+ }
+ else if (dz2 > dz1 && dz1 < 0x4000)
+ {
+ zvel[pFX->index] = -Random((klabs(pGFX->at11)<<18)/120);
+ }
+ else
+ {
+ if ((pGFX->at11<<18)/120 < 0)
+ zvel[pFX->index] = -Random((klabs(pGFX->at11)<<18)/120);
+ else
+ zvel[pFX->index] = Random2((pGFX->at11<<18)/120);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
+void GibThing(spritetype *pSprite, GIBTHING *pGThing, CGibPosition *pPos, CGibVelocity *pVel)
+{
+ if (gbAdultContent && gGameOptions.nGameType <= 0)
+ switch (pGThing->at0)
+ {
+ case 425:
+ case 427:
+ return;
+ }
+
+ if (pGThing->chance == 65536 || Chance(pGThing->chance))
+ {
+ int nSector = pSprite->sectnum;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int x, y, z;
+ if (!pPos)
+ {
+ int nAngle = Random(2048);
+ x = pSprite->x+mulscale30(pSprite->clipdist<<2, Cos(nAngle));
+ y = pSprite->y+mulscale30(pSprite->clipdist<<2, Sin(nAngle));
+ z = bottom-Random(bottom-top);
+ }
+ else
+ {
+ x = pPos->x;
+ y = pPos->y;
+ z = pPos->z;
+ }
+ int32_t ceilZ, floorZ;
+ getzsofslope(nSector, x, y, &ceilZ, &floorZ);
+ int dz1 = floorZ-z;
+ int dz2 = z-ceilZ;
+ spritetype *pGib = actSpawnThing(nSector, x, y, z, pGThing->at0);
+ dassert(pGib != NULL);
+ if (pGThing->at4 > -1)
+ pGib->picnum = pGThing->at4;
+ if (pVel)
+ {
+ xvel[pGib->index] = pVel->vx+Random2(pGThing->atc);
+ yvel[pGib->index] = pVel->vy+Random2(pGThing->atc);
+ zvel[pGib->index] = pVel->vz-Random(pGThing->at10);
+ }
+ else
+ {
+ xvel[pGib->index] = Random2((pGThing->atc<<18)/120);
+ yvel[pGib->index] = Random2((pGThing->atc<<18)/120);
+ switch (pSprite->cstat&48)
+ {
+ case 16:
+ zvel[pGib->index] = Random2((pGThing->at10<<18)/120);
+ break;
+ default:
+ if (dz2 < dz1 && dz2 < 0x4000)
+ {
+ zvel[pGib->index] = 0;
+ }
+ else if (dz2 > dz1 && dz1 < 0x4000)
+ {
+ zvel[pGib->index] = -Random((pGThing->at10<<18)/120);
+ }
+ else
+ {
+ zvel[pGib->index] = Random2((pGThing->at10<<18)/120);
+ }
+ break;
+ }
+ }
+ }
+}
+
+void GibSprite(spritetype *pSprite, GIBTYPE nGibType, CGibPosition *pPos, CGibVelocity *pVel)
+{
+ dassert(pSprite != NULL);
+ dassert(nGibType >= 0 && nGibType < kGibMax);
+ if (pSprite->sectnum < 0 || pSprite->sectnum >= numsectors)
+ return;
+ GIBLIST *pGib = &gibList[nGibType];
+ for (int i = 0; i < pGib->at4; i++)
+ {
+ GIBFX *pGibFX = &pGib->at0[i];
+ dassert(pGibFX->chance > 0);
+ GibFX(pSprite, pGibFX, pPos, pVel);
+ }
+ for (int i = 0; i < pGib->atc; i++)
+ {
+ GIBTHING *pGibThing = &pGib->at8[i];
+ dassert(pGibThing->chance > 0);
+ GibThing(pSprite, pGibThing, pPos, pVel);
+ }
+}
+
+void GibFX(int nWall, GIBFX * pGFX, int a3, int a4, int a5, int a6, CGibVelocity * pVel)
+{
+ dassert(nWall >= 0 && nWall < numwalls);
+ walltype *pWall = &wall[nWall];
+ int nCount = ChanceToCount(pGFX->chance, pGFX->at9);
+ int nSector = sectorofwall(nWall);
+ for (int i = 0; i < nCount; i++)
+ {
+ int r1 = Random(a6);
+ int r2 = Random(a5);
+ int r3 = Random(a4);
+ spritetype *pGib = gFX.fxSpawn(pGFX->at0, nSector, pWall->x+r3, pWall->y+r2, a3+r1, 0);
+ if (pGib)
+ {
+ if (pGFX->at1 < 0)
+ pGib->pal = pWall->pal;
+ if (!pVel)
+ {
+ xvel[pGib->index] = Random2((pGFX->atd<<18)/120);
+ yvel[pGib->index] = Random2((pGFX->atd<<18)/120);
+ zvel[pGib->index] = -Random((pGFX->at11<<18)/120);
+ }
+ else
+ {
+ xvel[pGib->index] = Random2((pVel->vx<<18)/120);
+ yvel[pGib->index] = Random2((pVel->vy<<18)/120);
+ zvel[pGib->index] = -Random((pVel->vz<<18)/120);
+ }
+ }
+ }
+}
+
+void GibWall(int nWall, GIBTYPE nGibType, CGibVelocity *pVel)
+{
+ dassert(nWall >= 0 && nWall < numwalls);
+ dassert(nGibType >= 0 && nGibType < kGibMax);
+ int cx, cy, cz, wx, wy, wz;
+ walltype *pWall = &wall[nWall];
+ cx = (pWall->x+wall[pWall->point2].x)>>1;
+ cy = (pWall->y+wall[pWall->point2].y)>>1;
+ int nSector = sectorofwall(nWall);
+ int32_t ceilZ, floorZ;
+ getzsofslope(nSector, cx, cy, &ceilZ, &floorZ);
+ int32_t ceilZ2, floorZ2;
+ getzsofslope(pWall->nextsector, cx, cy, &ceilZ2, &floorZ2);
+ ceilZ = ClipLow(ceilZ, ceilZ2);
+ floorZ = ClipHigh(floorZ, floorZ2);
+ wz = floorZ-ceilZ;
+ wx = wall[pWall->point2].x-pWall->x;
+ wy = wall[pWall->point2].y-pWall->y;
+ cz = (ceilZ+floorZ)>>1;
+ GIBLIST *pGib = &gibList[nGibType];
+ sfxPlay3DSound(cx, cy, cz, pGib->at10, nSector);
+ for (int i = 0; i < pGib->at4; i++)
+ {
+ GIBFX *pGibFX = &pGib->at0[i];
+ dassert(pGibFX->chance > 0);
+ GibFX(nWall, pGibFX, ceilZ, wx, wy, wz, pVel);
+ }
+}
diff --git a/source/blood/src/gib.h b/source/blood/src/gib.h
new file mode 100644
index 000000000..70dee7135
--- /dev/null
+++ b/source/blood/src/gib.h
@@ -0,0 +1,75 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+
+enum GIBTYPE {
+ GIBTYPE_0 = 0,
+ GIBTYPE_1,
+ GIBTYPE_2,
+ GIBTYPE_3,
+ GIBTYPE_4,
+ GIBTYPE_5,
+ GIBTYPE_6,
+ GIBTYPE_7,
+ GIBTYPE_8,
+ GIBTYPE_9,
+ GIBTYPE_10,
+ GIBTYPE_11,
+ GIBTYPE_12,
+ GIBTYPE_13,
+ GIBTYPE_14,
+ GIBTYPE_15,
+ GIBTYPE_16,
+ GIBTYPE_17,
+ GIBTYPE_18,
+ GIBTYPE_19,
+ GIBTYPE_20,
+ GIBTYPE_21,
+ GIBTYPE_22,
+ GIBTYPE_23,
+ GIBTYPE_24,
+ GIBTYPE_25,
+ GIBTYPE_26,
+ GIBTYPE_27,
+ GIBTYPE_28,
+ GIBTYPE_29,
+ GIBTYPE_30,
+ kGibMax
+};
+
+class CGibPosition {
+public:
+ int x, y, z;
+ CGibPosition(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {}
+};
+
+class CGibVelocity {
+public:
+ int vx, vy, vz;
+ CGibVelocity(int _vx, int _vy, int _vz) : vx(_vx), vy(_vy), vz(_vz) {}
+};
+
+void GibSprite(spritetype *pSprite, GIBTYPE nGibType, CGibPosition *pPos, CGibVelocity *pVel);
+//void GibFX(int nWall, GIBFX * pGFX, int a3, int a4, int a5, int a6, CGibVelocity * pVel);
+void GibWall(int nWall, GIBTYPE nGibType, CGibVelocity *pVel);
diff --git a/source/blood/src/globals.cpp b/source/blood/src/globals.cpp
new file mode 100644
index 000000000..6fe8ad9a8
--- /dev/null
+++ b/source/blood/src/globals.cpp
@@ -0,0 +1,55 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+
+#include "compat.h"
+#include "blood.h"
+
+int gFrameClock;
+int gFrameTicks;
+int gFrame;
+int volatile gGameClock;
+int gFrameRate;
+int gGamma;
+int gSaveGameNum;
+
+bool gQuitGame;
+int gQuitRequest;
+bool gPaused;
+bool gSaveGameActive;
+int gCacheMiss;
+
+char *gVersionString;
+char gVersionStringBuf[16];
+
+const char *GetVersionString(void)
+{
+ if (!gVersionString)
+ {
+ gVersionString = gVersionStringBuf;
+ if (!gVersionString)
+ return NULL;
+ sprintf(gVersionString, "%d.%02d", EXEVERSION / 100, EXEVERSION % 100);
+ }
+ return gVersionString;
+}
\ No newline at end of file
diff --git a/source/blood/src/globals.h b/source/blood/src/globals.h
new file mode 100644
index 000000000..085a25167
--- /dev/null
+++ b/source/blood/src/globals.h
@@ -0,0 +1,41 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+extern int gFrameClock;
+extern int gFrameTicks;
+extern int gFrame;
+extern int volatile gGameClock;
+extern int gFrameRate;
+extern int gGamma;
+extern int gSaveGameNum;
+
+
+extern bool gPaused;
+extern bool gSaveGameActive;
+extern bool gSavingGame;
+extern bool gQuitGame;
+extern int gQuitRequest;
+extern int gCacheMiss;
+
+const char *GetVersionString(void);
\ No newline at end of file
diff --git a/source/blood/src/gmtimbre.cpp b/source/blood/src/gmtimbre.cpp
new file mode 100644
index 000000000..6c751a82d
--- /dev/null
+++ b/source/blood/src/gmtimbre.cpp
@@ -0,0 +1,297 @@
+/*
+Copyright (C) 1994-1995 Apogee Software, Ltd.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include
+
+namespace OPLMusic {
+
+typedef struct
+ {
+ uint8_t SAVEK[ 2 ];
+ uint8_t Level[ 2 ];
+ uint8_t Env1[ 2 ];
+ uint8_t Env2[ 2 ];
+ uint8_t Wave[ 2 ];
+ uint8_t Feedback;
+ int8_t Transpose;
+ int8_t Velocity;
+ } TIMBRE;
+
+TIMBRE ADLIB_TimbreBank[ 256 ] =
+ {
+ { { 33, 33 }, { 143, 6 }, { 242, 242 }, { 69, 118 }, { 0, 0 }, 8, 0, 0 },
+ { { 49, 33 }, { 75, 0 }, { 242, 242 }, { 84, 86 }, { 0, 0 }, 8, 0, 0 },
+ { { 49, 33 }, { 73, 0 }, { 242, 242 }, { 85, 118 }, { 0, 0 }, 8, 0, 0 },
+ { { 177, 97 }, { 14, 0 }, { 242, 243 }, { 59, 11 }, { 0, 0 }, 6, 0, 0 },
+ { { 1, 33 }, { 87, 0 }, { 241, 241 }, { 56, 40 }, { 0, 0 }, 0, 0, 0 },
+ { { 1, 33 }, { 147, 0 }, { 241, 241 }, { 56, 40 }, { 0, 0 }, 0, 0, 0 },
+ { { 33, 54 }, { 128, 14 }, { 162, 241 }, { 1, 213 }, { 0, 0 }, 8, 0, 0 },
+ { { 1, 1 }, { 146, 0 }, { 194, 194 }, { 168, 88 }, { 0, 0 }, 10, 0, 0 },
+ { { 12, 129 }, { 92, 0 }, { 246, 243 }, { 84, 181 }, { 0, 0 }, 0, 0, 0 },
+ { { 7, 17 }, { 151, 128 }, { 246, 245 }, { 50, 17 }, { 0, 0 }, 2, 0, 0 },
+ { { 23, 1 }, { 33, 0 }, { 86, 246 }, { 4, 4 }, { 0, 0 }, 2, 0, 0 },
+ { { 24, 129 }, { 98, 0 }, { 243, 242 }, { 230, 246 }, { 0, 0 }, 0, 0, 0 },
+ { { 24, 33 }, { 35, 0 }, { 247, 229 }, { 85, 216 }, { 0, 0 }, 0, 0, 0 },
+ { { 21, 1 }, { 145, 0 }, { 246, 246 }, { 166, 230 }, { 0, 0 }, 4, 0, 0 },
+ { { 69, 129 }, { 89, 128 }, { 211, 163 }, { 130, 227 }, { 0, 0 }, 12, 0, 0 },
+ { { 3, 129 }, { 73, 128 }, { 116, 179 }, { 85, 5 }, { 1, 0 }, 4, 0, 0 },
+ { { 113, 49 }, { 146, 0 }, { 246, 241 }, { 20, 7 }, { 0, 0 }, 2, 0, 0 },
+ { { 114, 48 }, { 20, 0 }, { 199, 199 }, { 88, 8 }, { 0, 0 }, 2, 0, 0 },
+ { { 112, 177 }, { 68, 0 }, { 170, 138 }, { 24, 8 }, { 0, 0 }, 4, 0, 0 },
+ { { 35, 177 }, { 147, 0 }, { 151, 85 }, { 35, 20 }, { 1, 0 }, 4, 0, 0 },
+ { { 97, 177 }, { 19, 128 }, { 151, 85 }, { 4, 4 }, { 1, 0 }, 0, 0, 0 },
+ { { 36, 177 }, { 72, 0 }, { 152, 70 }, { 42, 26 }, { 1, 0 }, 12, 0, 0 },
+ { { 97, 33 }, { 19, 0 }, { 145, 97 }, { 6, 7 }, { 1, 0 }, 10, 0, 0 },
+ { { 33, 161 }, { 19, 137 }, { 113, 97 }, { 6, 7 }, { 0, 0 }, 6, 0, 0 },
+ { { 2, 65 }, { 156, 128 }, { 243, 243 }, { 148, 200 }, { 1, 0 }, 12, 0, 0 },
+ { { 3, 17 }, { 84, 0 }, { 243, 241 }, { 154, 231 }, { 1, 0 }, 12, 0, 0 },
+ { { 35, 33 }, { 95, 0 }, { 241, 242 }, { 58, 248 }, { 0, 0 }, 0, 0, 0 },
+ { { 3, 33 }, { 135, 128 }, { 246, 243 }, { 34, 243 }, { 1, 0 }, 6, 0, 0 },
+ { { 3, 33 }, { 71, 0 }, { 249, 246 }, { 84, 58 }, { 0, 0 }, 0, 0, 0 },
+ { { 35, 33 }, { 72, 0 }, { 149, 132 }, { 25, 25 }, { 1, 0 }, 8, 0, 0 },
+ { { 35, 33 }, { 74, 0 }, { 149, 148 }, { 25, 25 }, { 1, 0 }, 8, 0, 0 },
+ { { 9, 132 }, { 161, 128 }, { 32, 209 }, { 79, 248 }, { 0, 0 }, 8, 0, 0 },
+ { { 33, 162 }, { 30, 0 }, { 148, 195 }, { 6, 166 }, { 0, 0 }, 2, 0, 0 },
+ { { 49, 49 }, { 18, 0 }, { 241, 241 }, { 40, 24 }, { 0, 0 }, 10, 0, 0 },
+ { { 49, 49 }, { 141, 0 }, { 241, 241 }, { 232, 120 }, { 0, 0 }, 10, 0, 0 },
+ { { 49, 50 }, { 91, 0 }, { 81, 113 }, { 40, 72 }, { 0, 0 }, 12, 0, 0 },
+ { { 1, 33 }, { 139, 64 }, { 161, 242 }, { 154, 223 }, { 0, 0 }, 8, 0, 0 },
+ { { 1, 33 }, { 137, 64 }, { 161, 242 }, { 154, 223 }, { 0, 0 }, 8, 0, 0 },
+ { { 49, 49 }, { 139, 0 }, { 244, 241 }, { 232, 120 }, { 0, 0 }, 10, 0, 0 },
+ { { 49, 49 }, { 18, 0 }, { 241, 241 }, { 40, 24 }, { 0, 0 }, 10, 0, 0 },
+ { { 49, 33 }, { 21, 0 }, { 221, 86 }, { 19, 38 }, { 1, 0 }, 8, 0, 0 },
+ { { 49, 33 }, { 22, 0 }, { 221, 102 }, { 19, 6 }, { 1, 0 }, 8, 0, 0 },
+ { { 113, 49 }, { 73, 0 }, { 209, 97 }, { 28, 12 }, { 1, 0 }, 8, 0, 0 },
+ { { 33, 35 }, { 77, 128 }, { 113, 114 }, { 18, 6 }, { 1, 0 }, 2, 0, 0 },
+ { { 241, 225 }, { 64, 0 }, { 241, 111 }, { 33, 22 }, { 1, 0 }, 2, 0, 0 },
+ { { 2, 1 }, { 26, 128 }, { 245, 133 }, { 117, 53 }, { 1, 0 }, 0, 0, 0 },
+ { { 2, 1 }, { 29, 128 }, { 245, 243 }, { 117, 244 }, { 1, 0 }, 0, 0, 0 },
+ { { 16, 17 }, { 65, 0 }, { 245, 242 }, { 5, 195 }, { 1, 0 }, 2, 0, 0 },
+ { { 33, 162 }, { 155, 1 }, { 177, 114 }, { 37, 8 }, { 1, 0 }, 14, 0, 0 },
+ { { 161, 33 }, { 152, 0 }, { 127, 63 }, { 3, 7 }, { 1, 1 }, 0, 0, 0 },
+ { { 161, 97 }, { 147, 0 }, { 193, 79 }, { 18, 5 }, { 0, 0 }, 10, 0, 0 },
+ { { 33, 97 }, { 24, 0 }, { 193, 79 }, { 34, 5 }, { 0, 0 }, 12, 0, 0 },
+ { { 49, 114 }, { 91, 131 }, { 244, 138 }, { 21, 5 }, { 0, 0 }, 0, 0, 0 },
+ { { 161, 97 }, { 144, 0 }, { 116, 113 }, { 57, 103 }, { 0, 0 }, 0, 0, 0 },
+ { { 113, 114 }, { 87, 0 }, { 84, 122 }, { 5, 5 }, { 0, 0 }, 12, 0, 0 },
+ { { 144, 65 }, { 0, 0 }, { 84, 165 }, { 99, 69 }, { 0, 0 }, 8, 0, 0 },
+ { { 33, 33 }, { 146, 1 }, { 133, 143 }, { 23, 9 }, { 0, 0 }, 12, 0, 0 },
+ { { 33, 33 }, { 148, 5 }, { 117, 143 }, { 23, 9 }, { 0, 0 }, 12, 0, 0 },
+ { { 33, 97 }, { 148, 0 }, { 118, 130 }, { 21, 55 }, { 0, 0 }, 12, 0, 0 },
+ { { 49, 33 }, { 67, 0 }, { 158, 98 }, { 23, 44 }, { 1, 1 }, 2, 0, 0 },
+ { { 33, 33 }, { 155, 0 }, { 97, 127 }, { 106, 10 }, { 0, 0 }, 2, 0, 0 },
+ { { 97, 34 }, { 138, 6 }, { 117, 116 }, { 31, 15 }, { 0, 0 }, 8, 0, 0 },
+ { { 161, 33 }, { 134, 13 }, { 114, 113 }, { 85, 24 }, { 1, 0 }, 0, 0, 0 },
+ { { 33, 33 }, { 77, 0 }, { 84, 166 }, { 60, 28 }, { 0, 0 }, 8, 0, 0 },
+ { { 49, 97 }, { 143, 0 }, { 147, 114 }, { 2, 11 }, { 1, 0 }, 8, 0, 0 },
+ { { 49, 97 }, { 142, 0 }, { 147, 114 }, { 3, 9 }, { 1, 0 }, 8, 0, 0 },
+ { { 49, 97 }, { 145, 0 }, { 147, 130 }, { 3, 9 }, { 1, 0 }, 10, 0, 0 },
+ { { 49, 97 }, { 142, 0 }, { 147, 114 }, { 15, 15 }, { 1, 0 }, 10, 0, 0 },
+ { { 33, 33 }, { 75, 0 }, { 170, 143 }, { 22, 10 }, { 1, 0 }, 8, 0, 0 },
+ { { 49, 33 }, { 144, 0 }, { 126, 139 }, { 23, 12 }, { 1, 1 }, 6, 0, 0 },
+ { { 49, 50 }, { 129, 0 }, { 117, 97 }, { 25, 25 }, { 1, 0 }, 0, 0, 0 },
+ { { 50, 33 }, { 144, 0 }, { 155, 114 }, { 33, 23 }, { 0, 0 }, 4, 0, 0 },
+ { { 225, 225 }, { 31, 0 }, { 133, 101 }, { 95, 26 }, { 0, 0 }, 0, 0, 0 },
+ { { 225, 225 }, { 70, 0 }, { 136, 101 }, { 95, 26 }, { 0, 0 }, 0, 0, 0 },
+ { { 161, 33 }, { 156, 0 }, { 117, 117 }, { 31, 10 }, { 0, 0 }, 2, 0, 0 },
+ { { 49, 33 }, { 139, 0 }, { 132, 101 }, { 88, 26 }, { 0, 0 }, 0, 0, 0 },
+ { { 225, 161 }, { 76, 0 }, { 102, 101 }, { 86, 38 }, { 0, 0 }, 0, 0, 0 },
+ { { 98, 161 }, { 203, 0 }, { 118, 85 }, { 70, 54 }, { 0, 0 }, 0, 0, 0 },
+ { { 98, 161 }, { 153, 0 }, { 87, 86 }, { 7, 7 }, { 0, 0 }, 11, 0, 0 },
+ { { 98, 161 }, { 147, 0 }, { 119, 118 }, { 7, 7 }, { 0, 0 }, 11, 0, 0 },
+ { { 34, 33 }, { 89, 0 }, { 255, 255 }, { 3, 15 }, { 2, 0 }, 0, 0, 0 },
+ { { 33, 33 }, { 14, 0 }, { 255, 255 }, { 15, 15 }, { 1, 1 }, 0, 0, 0 },
+ { { 34, 33 }, { 70, 128 }, { 134, 100 }, { 85, 24 }, { 0, 0 }, 0, 0, 0 },
+ { { 33, 161 }, { 69, 0 }, { 102, 150 }, { 18, 10 }, { 0, 0 }, 0, 0, 0 },
+ { { 33, 34 }, { 139, 0 }, { 146, 145 }, { 42, 42 }, { 1, 0 }, 0, 0, 0 },
+ { { 162, 97 }, { 158, 64 }, { 223, 111 }, { 5, 7 }, { 0, 0 }, 2, 0, 0 },
+ { { 32, 96 }, { 26, 0 }, { 239, 143 }, { 1, 6 }, { 0, 2 }, 0, 0, 0 },
+ { { 33, 33 }, { 143, 128 }, { 241, 244 }, { 41, 9 }, { 0, 0 }, 10, 0, 0 },
+ { { 119, 161 }, { 165, 0 }, { 83, 160 }, { 148, 5 }, { 0, 0 }, 2, 0, 0 },
+ { { 97, 177 }, { 31, 128 }, { 168, 37 }, { 17, 3 }, { 0, 0 }, 10, 0, 0 },
+ { { 97, 97 }, { 23, 0 }, { 145, 85 }, { 52, 22 }, { 0, 0 }, 12, 0, 0 },
+ { { 113, 114 }, { 93, 0 }, { 84, 106 }, { 1, 3 }, { 0, 0 }, 0, 0, 0 },
+ { { 33, 162 }, { 151, 0 }, { 33, 66 }, { 67, 53 }, { 0, 0 }, 8, 0, 0 },
+ { { 161, 33 }, { 28, 0 }, { 161, 49 }, { 119, 71 }, { 1, 1 }, 0, 0, 0 },
+ { { 33, 97 }, { 137, 3 }, { 17, 66 }, { 51, 37 }, { 0, 0 }, 10, 0, 0 },
+ { { 161, 33 }, { 21, 0 }, { 17, 207 }, { 71, 7 }, { 1, 0 }, 0, 0, 0 },
+ { { 58, 81 }, { 206, 0 }, { 248, 134 }, { 246, 2 }, { 0, 0 }, 2, 0, 0 },
+ { { 33, 33 }, { 21, 0 }, { 33, 65 }, { 35, 19 }, { 1, 0 }, 0, 0, 0 },
+ { { 6, 1 }, { 91, 0 }, { 116, 165 }, { 149, 114 }, { 0, 0 }, 0, 0, 0 },
+ { { 34, 97 }, { 146, 131 }, { 177, 242 }, { 129, 38 }, { 0, 0 }, 12, 0, 0 },
+ { { 65, 66 }, { 77, 0 }, { 241, 242 }, { 81, 245 }, { 1, 0 }, 0, 0, 0 },
+ { { 97, 163 }, { 148, 128 }, { 17, 17 }, { 81, 19 }, { 1, 0 }, 6, 0, 0 },
+ { { 97, 161 }, { 140, 128 }, { 17, 29 }, { 49, 3 }, { 0, 0 }, 6, 0, 0 },
+ { { 164, 97 }, { 76, 0 }, { 243, 129 }, { 115, 35 }, { 1, 0 }, 4, 0, 0 },
+ { { 2, 7 }, { 133, 3 }, { 210, 242 }, { 83, 246 }, { 0, 1 }, 0, 0, 0 },
+ { { 17, 19 }, { 12, 128 }, { 163, 162 }, { 17, 229 }, { 1, 0 }, 0, 0, 0 },
+ { { 17, 17 }, { 6, 0 }, { 246, 242 }, { 65, 230 }, { 1, 2 }, 4, 0, 0 },
+ { { 147, 145 }, { 145, 0 }, { 212, 235 }, { 50, 17 }, { 0, 1 }, 8, 0, 0 },
+ { { 4, 1 }, { 79, 0 }, { 250, 194 }, { 86, 5 }, { 0, 0 }, 12, 0, 0 },
+ { { 33, 34 }, { 73, 0 }, { 124, 111 }, { 32, 12 }, { 0, 1 }, 6, 0, 0 },
+ { { 49, 33 }, { 133, 0 }, { 221, 86 }, { 51, 22 }, { 1, 0 }, 10, 0, 0 },
+ { { 32, 33 }, { 4, 129 }, { 218, 143 }, { 5, 11 }, { 2, 0 }, 6, 0, 0 },
+ { { 5, 3 }, { 106, 128 }, { 241, 195 }, { 229, 229 }, { 0, 0 }, 6, 0, 0 },
+ { { 7, 2 }, { 21, 0 }, { 236, 248 }, { 38, 22 }, { 0, 0 }, 10, 0, 0 },
+ { { 5, 1 }, { 157, 0 }, { 103, 223 }, { 53, 5 }, { 0, 0 }, 8, 0, 0 },
+ { { 24, 18 }, { 150, 0 }, { 250, 248 }, { 40, 229 }, { 0, 0 }, 10, 0, 0 },
+ { { 16, 0 }, { 134, 3 }, { 168, 250 }, { 7, 3 }, { 0, 0 }, 6, 0, 0 },
+ { { 17, 16 }, { 65, 3 }, { 248, 243 }, { 71, 3 }, { 2, 0 }, 4, 0, 0 },
+ { { 1, 16 }, { 142, 0 }, { 241, 243 }, { 6, 2 }, { 2, 0 }, 14, 0, 0 },
+ { { 14, 192 }, { 0, 0 }, { 31, 31 }, { 0, 255 }, { 0, 3 }, 14, 0, 0 },
+ { { 6, 3 }, { 128, 136 }, { 248, 86 }, { 36, 132 }, { 0, 2 }, 14, 0, 0 },
+ { { 14, 208 }, { 0, 5 }, { 248, 52 }, { 0, 4 }, { 0, 3 }, 14, 0, 0 },
+ { { 14, 192 }, { 0, 0 }, { 246, 31 }, { 0, 2 }, { 0, 3 }, 14, 0, 0 },
+ { { 213, 218 }, { 149, 64 }, { 55, 86 }, { 163, 55 }, { 0, 0 }, 0, 0, 0 },
+ { { 53, 20 }, { 92, 8 }, { 178, 244 }, { 97, 21 }, { 2, 0 }, 10, 0, 0 },
+ { { 14, 208 }, { 0, 0 }, { 246, 79 }, { 0, 245 }, { 0, 3 }, 14, 0, 0 },
+ { { 38, 228 }, { 0, 0 }, { 255, 18 }, { 1, 22 }, { 0, 1 }, 14, 0, 0 },
+ { { 0, 0 }, { 0, 0 }, { 243, 246 }, { 240, 201 }, { 0, 2 }, 14, 0, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 0, 0 }, { 0, 0 }, { 252, 250 }, { 5, 23 }, { 2, 0 }, 14, 52, 0 },
+ { { 0, 1 }, { 2, 0 }, { 255, 255 }, { 7, 8 }, { 0, 0 }, 0, 48, 0 },
+ { { 0, 0 }, { 0, 0 }, { 252, 250 }, { 5, 23 }, { 2, 0 }, 14, 58, 0 },
+ { { 0, 0 }, { 0, 0 }, { 246, 246 }, { 12, 6 }, { 0, 0 }, 4, 60, 0 },
+ { { 12, 18 }, { 0, 0 }, { 246, 251 }, { 8, 71 }, { 0, 2 }, 10, 47, 0 },
+ { { 0, 0 }, { 3, 0 }, { 248, 246 }, { 42, 69 }, { 0, 1 }, 4, 43, 0 },
+ { { 12, 18 }, { 0, 0 }, { 246, 251 }, { 8, 71 }, { 0, 2 }, 10, 49, 0 },
+ { { 0, 0 }, { 3, 0 }, { 248, 246 }, { 42, 69 }, { 0, 1 }, 4, 43, 0 },
+ { { 12, 18 }, { 0, 0 }, { 246, 251 }, { 8, 71 }, { 0, 2 }, 10, 51, 0 },
+ { { 0, 0 }, { 3, 0 }, { 248, 246 }, { 42, 69 }, { 0, 1 }, 4, 43, 0 },
+ { { 12, 18 }, { 0, 0 }, { 246, 251 }, { 8, 71 }, { 0, 2 }, 10, 54, 0 },
+ { { 12, 18 }, { 0, 0 }, { 246, 251 }, { 8, 71 }, { 0, 2 }, 10, 57, 0 },
+ { { 0, 0 }, { 3, 0 }, { 248, 246 }, { 42, 69 }, { 0, 1 }, 4, 72, 0 },
+ { { 12, 18 }, { 0, 0 }, { 246, 251 }, { 8, 71 }, { 0, 2 }, 10, 60, 0 },
+ { { 14, 208 }, { 0, 10 }, { 245, 159 }, { 48, 2 }, { 0, 0 }, 14, 76, 0 },
+ { { 14, 7 }, { 10, 93 }, { 228, 245 }, { 228, 229 }, { 3, 1 }, 6, 84, 0 },
+ { { 2, 5 }, { 3, 10 }, { 180, 151 }, { 4, 247 }, { 0, 0 }, 14, 36, 0 },
+ { { 78, 158 }, { 0, 0 }, { 246, 159 }, { 0, 2 }, { 0, 3 }, 14, 76, 0 },
+ { { 17, 16 }, { 69, 8 }, { 248, 243 }, { 55, 5 }, { 2, 0 }, 8, 84, 0 },
+ { { 14, 208 }, { 0, 0 }, { 246, 159 }, { 0, 2 }, { 0, 3 }, 14, 83, 0 },
+ { { 128, 16 }, { 0, 13 }, { 255, 255 }, { 3, 20 }, { 3, 0 }, 12, 84, 0 },
+ { { 14, 7 }, { 8, 81 }, { 248, 244 }, { 66, 228 }, { 0, 3 }, 14, 24, 0 },
+ { { 14, 208 }, { 0, 10 }, { 245, 159 }, { 48, 2 }, { 0, 0 }, 14, 77, 0 },
+ { { 1, 2 }, { 0, 0 }, { 250, 200 }, { 191, 151 }, { 0, 0 }, 7, 60, 0 },
+ { { 1, 1 }, { 81, 0 }, { 250, 250 }, { 135, 183 }, { 0, 0 }, 6, 65, 0 },
+ { { 1, 2 }, { 84, 0 }, { 250, 248 }, { 141, 184 }, { 0, 0 }, 6, 59, 0 },
+ { { 1, 2 }, { 89, 0 }, { 250, 248 }, { 136, 182 }, { 0, 0 }, 6, 51, 0 },
+ { { 1, 0 }, { 0, 0 }, { 249, 250 }, { 10, 6 }, { 3, 0 }, 14, 45, 0 },
+ { { 0, 0 }, { 128, 0 }, { 249, 246 }, { 137, 108 }, { 3, 0 }, 14, 71, 0 },
+ { { 3, 12 }, { 128, 8 }, { 248, 246 }, { 136, 182 }, { 3, 0 }, 15, 60, 0 },
+ { { 3, 12 }, { 133, 0 }, { 248, 246 }, { 136, 182 }, { 3, 0 }, 15, 58, 0 },
+ { { 14, 0 }, { 64, 8 }, { 118, 119 }, { 79, 24 }, { 0, 2 }, 14, 53, 0 },
+ { { 14, 3 }, { 64, 0 }, { 200, 155 }, { 73, 105 }, { 0, 2 }, 14, 64, 0 },
+ { { 215, 199 }, { 220, 0 }, { 173, 141 }, { 5, 5 }, { 3, 0 }, 14, 71, 0 },
+ { { 215, 199 }, { 220, 0 }, { 168, 136 }, { 4, 4 }, { 3, 0 }, 14, 61, 0 },
+ { { 128, 17 }, { 0, 0 }, { 246, 103 }, { 6, 23 }, { 3, 3 }, 14, 61, 0 },
+ { { 128, 17 }, { 0, 9 }, { 245, 70 }, { 5, 22 }, { 2, 3 }, 14, 48, 0 },
+ { { 6, 21 }, { 63, 0 }, { 0, 247 }, { 244, 245 }, { 0, 0 }, 1, 48, 0 },
+ { { 6, 18 }, { 63, 0 }, { 0, 247 }, { 244, 245 }, { 3, 0 }, 0, 69, 0 },
+ { { 6, 18 }, { 63, 0 }, { 0, 247 }, { 244, 245 }, { 0, 0 }, 1, 68, 0 },
+ { { 1, 2 }, { 88, 0 }, { 103, 117 }, { 231, 7 }, { 0, 0 }, 0, 63, 0 },
+ { { 65, 66 }, { 69, 8 }, { 248, 117 }, { 72, 5 }, { 0, 0 }, 0, 74, 0 },
+ { { 10, 30 }, { 64, 78 }, { 224, 255 }, { 240, 5 }, { 3, 0 }, 8, 60, 0 },
+ { { 10, 30 }, { 124, 82 }, { 224, 255 }, { 240, 2 }, { 3, 0 }, 8, 80, 0 },
+ { { 14, 0 }, { 64, 8 }, { 122, 123 }, { 74, 27 }, { 0, 2 }, 14, 64, 0 },
+ { { 14, 7 }, { 10, 64 }, { 228, 85 }, { 228, 57 }, { 3, 1 }, 6, 69, 0 },
+ { { 5, 4 }, { 5, 64 }, { 249, 214 }, { 50, 165 }, { 3, 0 }, 14, 73, 0 },
+ { { 2, 21 }, { 63, 0 }, { 0, 247 }, { 243, 245 }, { 3, 0 }, 8, 75, 0 },
+ { { 1, 2 }, { 79, 0 }, { 250, 248 }, { 141, 181 }, { 0, 0 }, 7, 68, 0 },
+ { { 0, 0 }, { 0, 0 }, { 246, 246 }, { 12, 6 }, { 0, 0 }, 4, 48, 0 },
+ { { 33, 17 }, { 17, 0 }, { 163, 196 }, { 67, 34 }, { 2, 0 }, 13, 53, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 },
+ { { 16, 17 }, { 68, 0 }, { 248, 243 }, { 119, 6 }, { 2, 0 }, 8, 35, 0 }
+ };
+
+}
diff --git a/source/blood/src/inifile.cpp b/source/blood/src/inifile.cpp
new file mode 100644
index 000000000..a3ee29364
--- /dev/null
+++ b/source/blood/src/inifile.cpp
@@ -0,0 +1,465 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 sirlemonhead, Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+// Note: This module is based on the sirlemonhead's work
+
+#include
+#include
+#include
+#include
+#include "common_game.h"
+
+#include "inifile.h"
+#include "misc.h"
+
+
+IniFile::IniFile(const char *fileName)
+{
+ head.next = &head;
+
+ // debug stuff
+ // curNode = NULL;
+ // anotherNode = NULL;
+ //_13 = NULL;
+
+ strcpy(this->fileName, fileName);
+ Load();
+}
+
+IniFile::IniFile(void *res)
+{
+ head.next = &head;
+ strcpy(fileName, "menus.mat");
+ LoadRes(res);
+}
+
+void IniFile::LoadRes(void *res)
+{
+ char buffer[256];
+
+ curNode = &head;
+
+ while (ResReadLine(buffer, sizeof(buffer), &res) != 0)
+ {
+ char *ch = strchr(buffer, '\n');
+ if (ch != NULL) {
+ ch[0] = '\0';
+ }
+
+ // do the same for carriage return?
+ ch = strchr(buffer, '\r');
+ if (ch != NULL) {
+ ch[0] = '\0';
+ }
+
+ char *pBuffer = buffer;
+
+ // remove whitespace from buffer
+ while (isspace(*pBuffer)) {
+ pBuffer++;
+ }
+
+ curNode->next = (FNODE*)malloc(strlen(pBuffer) + sizeof(FNODE));
+ dassert(curNode->next != NULL);
+
+ anotherNode = curNode;
+ curNode = curNode->next;
+
+
+ strcpy(curNode->name, pBuffer);
+
+ /*
+ check for:
+ ; - comment line. continue and grab a new line (59)
+ [ - start of section marker (91)
+ ] - end of section marker (93)
+ = - key and value seperator (61)
+ */
+
+ switch (*pBuffer)
+ {
+ case 0:
+ case ';': // comment line
+ break;
+ case '[':
+ if (!strchr(pBuffer, ']'))
+ {
+ free(curNode);
+ curNode = anotherNode;
+ }
+ break;
+ default:
+
+ if (strchr(pBuffer, '=') <= pBuffer) {
+ free(curNode);
+ curNode = anotherNode;
+ }
+ break;
+ }
+ }
+
+ curNode->next = &head;
+}
+
+void IniFile::Load()
+{
+ // char buffer[256];
+
+ curNode = &head;
+
+ int fp = kopen4loadfrommod(fileName, 0);
+ if (fp >= 0)
+ {
+ int nSize = kfilelength(fp);
+ void *pBuffer = Xcalloc(1, nSize + 1);
+ kread(fp, pBuffer, nSize);
+ LoadRes(pBuffer);
+ Bfree(pBuffer);
+ }
+ else
+ curNode->next = &head;
+#if 0
+ if (fp)
+ {
+ while (fgets(buffer, sizeof(buffer), fp) != NULL)
+ {
+ char *ch = strchr(buffer, '\n');
+ if (ch != NULL) {
+ ch[0] = '\0';
+ }
+
+ // do the same for carriage return?
+ ch = strchr(buffer, '\r');
+ if (ch != NULL) {
+ ch[0] = '\0';
+ }
+
+ char *pBuffer = buffer;
+
+ // remove whitespace from buffer
+ while (isspace(*pBuffer)) {
+ pBuffer++;
+ }
+
+ curNode->next = (FNODE*)malloc(strlen(pBuffer) + sizeof(FNODE));
+ dassert(curNode->next != NULL);
+
+ anotherNode = curNode;
+ curNode = curNode->next;
+
+
+ strcpy(curNode->name, pBuffer);
+
+ /*
+ check for:
+ ; - comment line. continue and grab a new line (59)
+ [ - start of section marker (91)
+ ] - end of section marker (93)
+ = - key and value seperator (61)
+ */
+
+ switch (*pBuffer)
+ {
+ case 0:
+ case ';': // comment line
+ break;
+ case '[':
+ if (!strchr(pBuffer, ']'))
+ {
+ free(curNode);
+ curNode = anotherNode;
+ }
+ break;
+ default:
+
+ if (strchr(pBuffer, '=') <= pBuffer) {
+ free(curNode);
+ curNode = anotherNode;
+ }
+ break;
+ }
+ }
+ fclose(fp);
+ }
+
+ curNode->next = &head;
+#endif
+}
+
+void IniFile::Save(void)
+{
+ char buffer[256];
+ FILE *hFile = fopen(fileName, "w");
+ dassert(hFile != NULL);
+ curNode = head.next;
+ while (curNode != &head)
+ {
+ sprintf(buffer, "%s\n", curNode->name);
+ fwrite(buffer, 1, strlen(buffer), hFile);
+ curNode = curNode->next;
+ }
+ fclose(hFile);
+}
+
+bool IniFile::FindSection(const char *section)
+{
+ char buffer[256];
+ curNode = anotherNode = &head;
+ if (section)
+ {
+ sprintf(buffer, "[%s]", section);
+ do
+ {
+ anotherNode = curNode;
+ curNode = curNode->next;
+ if (curNode == &head)
+ return false;
+ } while(Bstrcasecmp(curNode->name, buffer) != 0);
+ }
+ return true;
+}
+
+bool IniFile::SectionExists(const char *section)
+{
+ return FindSection(section);
+}
+
+bool IniFile::FindKey(const char *key)
+{
+ anotherNode = curNode;
+ curNode = curNode->next;
+ while (curNode != &head)
+ {
+ char c = curNode->name[0];
+
+ if (c == ';' || c == '\0') {
+ anotherNode = curNode;
+ curNode = curNode->next;
+ continue;
+ }
+
+ if (c == '[') {
+ return 0;
+ }
+
+ char *pEqual = strchr(curNode->name, '=');
+ char *pEqualStart = pEqual;
+ dassert(pEqual != NULL);
+
+ // remove whitespace
+ while (isspace(*(pEqual - 1))) {
+ pEqual--;
+ }
+
+ c = *pEqual;
+ *pEqual = '\0';
+
+ if (strcmp(key, curNode->name) == 0)
+ {
+ // strings match
+ *pEqual = c;
+ _13 = ++pEqualStart;
+ while (isspace(*_13)) {
+ _13++;
+ }
+
+ return true;
+ }
+ *pEqual = c;
+ anotherNode = curNode;
+ curNode = curNode->next;
+ }
+
+ return false;
+}
+
+void IniFile::AddSection(const char *section)
+{
+ char buffer[256];
+
+ if (anotherNode != &head)
+ {
+ FNODE *newNode = (FNODE*)malloc(sizeof(FNODE));
+ dassert(newNode != NULL);
+
+ newNode->name[0] = 0;
+ newNode->next = anotherNode->next;
+ anotherNode->next = newNode;
+ anotherNode = newNode;
+ }
+
+ sprintf(buffer, "[%s]", section);
+ FNODE *newNode = (FNODE*)malloc(strlen(buffer) + sizeof(FNODE));
+ dassert(newNode != NULL);
+
+ strcpy(newNode->name, buffer);
+
+ newNode->next = anotherNode->next;
+ anotherNode->next = newNode;
+ anotherNode = newNode;
+}
+
+void IniFile::AddKeyString(const char *key, const char *value)
+{
+ char buffer[256];
+
+ sprintf(buffer, "%s=%s", key, value);
+
+ FNODE *newNode = (FNODE*)malloc(strlen(buffer) + sizeof(FNODE));
+ dassert(newNode != NULL);
+
+ strcpy(newNode->name, buffer);
+
+ newNode->next = anotherNode->next;
+ anotherNode->next = newNode;
+ curNode = newNode;
+}
+
+void IniFile::ChangeKeyString(const char *key, const char *value)
+{
+ char buffer[256];
+
+ sprintf(buffer, "%s=%s", key, value);
+
+ FNODE *newNode = (FNODE*)realloc(curNode, strlen(buffer) + sizeof(FNODE));
+ dassert(newNode != NULL);
+
+ strcpy(newNode->name, buffer);
+
+ anotherNode->next = newNode;
+}
+
+bool IniFile::KeyExists(const char *section, const char *key)
+{
+ if (FindSection(section) && FindKey(key))
+ return true;
+
+ return false;
+}
+
+void IniFile::PutKeyString(const char *section, const char *key, const char *value)
+{
+ if (FindSection(section))
+ {
+ if (FindKey(key))
+ {
+ ChangeKeyString(key, value);
+ return;
+ }
+ }
+ else
+ {
+ AddSection(section);
+ }
+
+ AddKeyString(key, value);
+}
+
+const char* IniFile::GetKeyString(const char *section, const char *key, const char *defaultValue)
+{
+ if (FindSection(section) && FindKey(key))
+ return _13;
+ return defaultValue;
+}
+
+void IniFile::PutKeyInt(const char *section, const char *key, int value)
+{
+ char buffer[256];
+
+ // convert int to string
+ sprintf(buffer,"%d",value);
+
+ PutKeyString(section, key, buffer);
+}
+
+int IniFile::GetKeyInt(const char *section, const char *key, int defaultValue)
+{
+ if (FindSection(section) && FindKey(key))
+ {
+ // convert string to int int
+ return strtol(_13, NULL, 0);
+ }
+ return defaultValue;
+}
+
+void IniFile::PutKeyHex(const char *section, const char *key, int value)
+{
+ char buffer[256] = "0x";
+
+ // convert int to string
+ sprintf(buffer,"%x",value);
+
+ PutKeyString(section, key, buffer);
+}
+
+int IniFile::GetKeyHex(const char *section, const char *key, int defaultValue)
+{
+ return GetKeyInt(section, key, defaultValue);
+}
+
+bool IniFile::GetKeyBool(const char *section, const char *key, int defaultValue)
+{
+ return (bool)GetKeyInt(section, key, defaultValue);
+}
+
+void IniFile::RemoveKey(const char *section, const char *key)
+{
+ if (FindSection(section) && FindKey(key))
+ {
+ anotherNode->next = curNode->next;
+ free(curNode);
+ curNode = anotherNode->next;
+ }
+}
+
+void IniFile::RemoveSection(const char *section)
+{
+ if (FindSection(section))
+ {
+ anotherNode = curNode;
+
+ curNode = curNode->next;
+
+ while (curNode != &head)
+ {
+ if (curNode->name[0] == '[') {
+ return;
+ }
+
+ anotherNode->next = curNode->next;
+ free(curNode);
+ curNode = anotherNode->next;
+ }
+ }
+}
+
+IniFile::~IniFile()
+{
+ curNode = head.next;
+
+ while (curNode != &head)
+ {
+ anotherNode = curNode;
+ curNode = curNode->next;
+ free(anotherNode);
+ }
+}
diff --git a/source/blood/src/inifile.h b/source/blood/src/inifile.h
new file mode 100644
index 000000000..2fe0490eb
--- /dev/null
+++ b/source/blood/src/inifile.h
@@ -0,0 +1,68 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 sirlemonhead, Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+// Note: This module is based on the sirlemonhead's work
+#pragma once
+struct FNODE
+{
+ FNODE *next;
+ char name[1];
+};
+
+// 161 bytes
+class IniFile
+{
+public:
+ IniFile(const char *fileName);
+ IniFile(void *res);
+ ~IniFile();
+
+ void Save(void);
+ bool FindSection(const char *section);
+ bool SectionExists(const char *section);
+ bool FindKey(const char *key);
+ void AddSection(const char *section);
+ void AddKeyString(const char *key, const char *value);
+ void ChangeKeyString(const char *key, const char *value);
+ bool KeyExists(const char *section, const char *key);
+ void PutKeyString(const char *section, const char *key, const char *value);
+ const char* GetKeyString(const char *section, const char *key, const char *defaultValue);
+ void PutKeyInt(const char *section, const char *key, const int value);
+ int GetKeyInt(const char *section, const char *key, int defaultValue);
+ void PutKeyHex(const char *section, const char *key, int value);
+ int GetKeyHex(const char *section, const char *key, int defaultValue);
+ bool GetKeyBool(const char *section, const char *key, int defaultValue);
+ void RemoveKey(const char *section, const char *key);
+ void RemoveSection(const char *section);
+
+private:
+ FNODE head;
+ FNODE *curNode;
+ FNODE *anotherNode;
+
+ char *_13;
+
+ char fileName[BMAX_PATH]; // watcom maxpath
+
+ void LoadRes(void *);
+ void Load();
+};
\ No newline at end of file
diff --git a/source/blood/src/iob.cpp b/source/blood/src/iob.cpp
new file mode 100644
index 000000000..e0c0accfa
--- /dev/null
+++ b/source/blood/src/iob.cpp
@@ -0,0 +1,74 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include "compat.h"
+#include "common_game.h"
+#include "iob.h"
+
+IOBuffer::IOBuffer(int _nRemain, char *_pBuffer)
+{
+ nRemain = _nRemain;
+ pBuffer =_pBuffer;
+}
+
+void IOBuffer::Read(void *pData, int nSize)
+{
+ if (nSize <= nRemain)
+ {
+ memcpy(pData, pBuffer, nSize);
+ nRemain -= nSize;
+ pBuffer += nSize;
+ }
+ else
+ {
+ ThrowError("Read buffer overflow");
+ }
+}
+
+void IOBuffer::Write(void *pData, int nSize)
+{
+ if (nSize <= nRemain)
+ {
+ memcpy(pBuffer, pData, nSize);
+ nRemain -= nSize;
+ pBuffer += nSize;
+ }
+ else
+ {
+ ThrowError("Write buffer overflow");
+ }
+}
+
+void IOBuffer::Skip(int nSize)
+{
+ if (nSize <= nRemain)
+ {
+ nRemain -= nSize;
+ pBuffer += nSize;
+ }
+ else
+ {
+ ThrowError("Skip overflow");
+ }
+}
diff --git a/source/blood/src/iob.h b/source/blood/src/iob.h
new file mode 100644
index 000000000..b9379866d
--- /dev/null
+++ b/source/blood/src/iob.h
@@ -0,0 +1,34 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+class IOBuffer
+{
+public:
+ IOBuffer(int _nRemain, char *pBuffer);
+ int nRemain;
+ char *pBuffer;
+ void Read(void *, int);
+ void Write(void *, int);
+ void Skip(int);
+};
\ No newline at end of file
diff --git a/source/blood/src/levels.cpp b/source/blood/src/levels.cpp
new file mode 100644
index 000000000..57b67f0ca
--- /dev/null
+++ b/source/blood/src/levels.cpp
@@ -0,0 +1,443 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include "compat.h"
+#include "common_game.h"
+
+#include "asound.h"
+#include "blood.h"
+#include "config.h"
+#include "credits.h"
+#include "endgame.h"
+#include "inifile.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "messages.h"
+#include "network.h"
+#include "screen.h"
+#include "seq.h"
+#include "sound.h"
+#include "sfx.h"
+#include "view.h"
+
+GAMEOPTIONS gGameOptions;
+
+GAMEOPTIONS gSingleGameOptions = {
+ 0, 2, 0, 0, "", "", 2, "", "", 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 3600, 1800, 1800, 7200
+};
+
+EPISODEINFO gEpisodeInfo[kMaxEpisodes+1];
+
+int gSkill = 2;
+int gEpisodeCount;
+int gNextLevel;
+bool gGameStarted;
+
+int gLevelTime;
+
+char BloodIniFile[BMAX_PATH] = "BLOOD.INI";
+char BloodIniPre[BMAX_PATH];
+bool bINIOverride = false;
+IniFile *BloodINI;
+
+
+void levelInitINI(const char *pzIni)
+{
+ int fp = kopen4loadfrommod(pzIni, 0);
+ if (fp < 0)
+ ThrowError("Initialization: %s does not exist", pzIni);
+ kclose(fp);
+ BloodINI = new IniFile(pzIni);
+ Bstrncpy(BloodIniFile, pzIni, BMAX_PATH);
+ Bstrncpy(BloodIniPre, pzIni, BMAX_PATH);
+ ChangeExtension(BloodIniPre, "");
+}
+
+
+void levelOverrideINI(const char *pzIni)
+{
+ bINIOverride = true;
+ strcpy(BloodIniFile, pzIni);
+}
+
+void levelPlayIntroScene(int nEpisode)
+{
+ gGameOptions.uGameFlags &= ~4;
+ sndStopSong();
+ sndKillAllSounds();
+ sfxKillAllSounds();
+ ambKillAll();
+ seqKillAll();
+ EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode];
+ credPlaySmk(pEpisode->at8f08, pEpisode->at9030, pEpisode->at9028);
+ scrSetDac();
+ viewResizeView(gViewSize);
+ credReset();
+ scrSetDac();
+}
+
+void levelPlayEndScene(int nEpisode)
+{
+ gGameOptions.uGameFlags &= ~8;
+ sndStopSong();
+ sndKillAllSounds();
+ sfxKillAllSounds();
+ ambKillAll();
+ seqKillAll();
+ EPISODEINFO *pEpisode = &gEpisodeInfo[nEpisode];
+ credPlaySmk(pEpisode->at8f98, pEpisode->at90c0, pEpisode->at902c);
+ scrSetDac();
+ viewResizeView(gViewSize);
+ credReset();
+ scrSetDac();
+}
+
+void levelClearSecrets(void)
+{
+ gSecretMgr.Clear();
+}
+
+void levelSetupSecret(int nCount)
+{
+ gSecretMgr.SetCount(nCount);
+}
+
+void levelTriggerSecret(int nSecret)
+{
+ gSecretMgr.Found(nSecret);
+}
+
+void CheckSectionAbend(const char *pzSection)
+{
+ if (!pzSection || !BloodINI->SectionExists(pzSection))
+ ThrowError("Section [%s] expected in BLOOD.INI", pzSection);
+}
+
+void CheckKeyAbend(const char *pzSection, const char *pzKey)
+{
+ dassert(pzSection != NULL);
+
+ if (!pzKey || !BloodINI->KeyExists(pzSection, pzKey))
+ ThrowError("Key %s expected in section [%s] of BLOOD.INI", pzKey, pzSection);
+}
+
+LEVELINFO * levelGetInfoPtr(int nEpisode, int nLevel)
+{
+ dassert(nEpisode >= 0 && nEpisode < gEpisodeCount);
+ EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[nEpisode];
+ dassert(nLevel >= 0 && nLevel < pEpisodeInfo->nLevels);
+ return &pEpisodeInfo->at28[nLevel];
+}
+
+char * levelGetFilename(int nEpisode, int nLevel)
+{
+ dassert(nEpisode >= 0 && nEpisode < gEpisodeCount);
+ EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[nEpisode];
+ dassert(nLevel >= 0 && nLevel < pEpisodeInfo->nLevels);
+ return pEpisodeInfo->at28[nLevel].at0;
+}
+
+char * levelGetMessage(int nMessage)
+{
+ int nEpisode = gGameOptions.nEpisode;
+ int nLevel = gGameOptions.nLevel;
+ dassert(nMessage < kMaxMessages);
+ char *pMessage = gEpisodeInfo[nEpisode].at28[nLevel].atec[nMessage];
+ if (*pMessage == 0)
+ return NULL;
+ return pMessage;
+}
+
+char * levelGetTitle(void)
+{
+ int nEpisode = gGameOptions.nEpisode;
+ int nLevel = gGameOptions.nLevel;
+ char *pTitle = gEpisodeInfo[nEpisode].at28[nLevel].at90;
+ if (*pTitle == 0)
+ return NULL;
+ return pTitle;
+}
+
+char * levelGetAuthor(void)
+{
+ int nEpisode = gGameOptions.nEpisode;
+ int nLevel = gGameOptions.nLevel;
+ char *pAuthor = gEpisodeInfo[nEpisode].at28[nLevel].atb0;
+ if (*pAuthor == 0)
+ return NULL;
+ return pAuthor;
+}
+
+void levelSetupOptions(int nEpisode, int nLevel)
+{
+ gGameOptions.nEpisode = nEpisode;
+ gGameOptions.nLevel = nLevel;
+ strcpy(gGameOptions.zLevelName, gEpisodeInfo[nEpisode].at28[nLevel].at0);
+ gGameOptions.uMapCRC = dbReadMapCRC(gGameOptions.zLevelName);
+ // strcpy(gGameOptions.zLevelSong, gEpisodeInfo[nEpisode].at28[nLevel].atd0);
+ gGameOptions.nTrackNumber = gEpisodeInfo[nEpisode].at28[nLevel].ate0;
+}
+
+void levelLoadMapInfo(IniFile *pIni, LEVELINFO *pLevelInfo, const char *pzSection)
+{
+ char buffer[16];
+ strncpy(pLevelInfo->at90, pIni->GetKeyString(pzSection, "Title", pLevelInfo->at0), 31);
+ strncpy(pLevelInfo->atb0, pIni->GetKeyString(pzSection, "Author", ""), 31);
+ strncpy(pLevelInfo->atd0, pIni->GetKeyString(pzSection, "Song", ""), BMAX_PATH);
+ pLevelInfo->ate0 = pIni->GetKeyInt(pzSection, "Track", -1);
+ pLevelInfo->ate4 = pIni->GetKeyInt(pzSection, "EndingA", -1);
+ pLevelInfo->ate8 = pIni->GetKeyInt(pzSection, "EndingB", -1);
+ pLevelInfo->at8ec = pIni->GetKeyInt(pzSection, "Fog", -0);
+ pLevelInfo->at8ed = pIni->GetKeyInt(pzSection, "Weather", -0);
+ for (int i = 0; i < kMaxMessages; i++)
+ {
+ sprintf(buffer, "Message%d", i+1);
+ strncpy(pLevelInfo->atec[i], pIni->GetKeyString(pzSection, buffer, ""), 63);
+ }
+}
+
+extern void MenuSetupEpisodeInfo(void);
+
+void levelLoadDefaults(void)
+{
+ char buffer[64];
+ char buffer2[16];
+ levelInitINI(pINISelected->zName);
+ memset(gEpisodeInfo, 0, sizeof(gEpisodeInfo));
+ strncpy(gEpisodeInfo[MUS_INTRO/kMaxLevels].at28[MUS_INTRO%kMaxLevels].atd0, "PESTIS", BMAX_PATH);
+ int i;
+ for (i = 0; i < kMaxEpisodes; i++)
+ {
+ sprintf(buffer, "Episode%d", i+1);
+ if (!BloodINI->SectionExists(buffer))
+ break;
+ EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[i];
+ strncpy(pEpisodeInfo->at0, BloodINI->GetKeyString(buffer, "Title", buffer), 31);
+ strncpy(pEpisodeInfo->at8f08, BloodINI->GetKeyString(buffer, "CutSceneA", ""), BMAX_PATH);
+ pEpisodeInfo->at9028 = BloodINI->GetKeyInt(buffer, "CutWavA", -1);
+ if (pEpisodeInfo->at9028 == 0)
+ strncpy(pEpisodeInfo->at9030, BloodINI->GetKeyString(buffer, "CutWavA", ""), BMAX_PATH);
+ else
+ pEpisodeInfo->at9030[0] = 0;
+ strncpy(pEpisodeInfo->at8f98, BloodINI->GetKeyString(buffer, "CutSceneB", ""), BMAX_PATH);
+ pEpisodeInfo->at902c = BloodINI->GetKeyInt(buffer, "CutWavB", -1);
+ if (pEpisodeInfo->at902c == 0)
+ strncpy(pEpisodeInfo->at90c0, BloodINI->GetKeyString(buffer, "CutWavB", ""), BMAX_PATH);
+ else
+ pEpisodeInfo->at90c0[0] = 0;
+
+ pEpisodeInfo->bloodbath = BloodINI->GetKeyInt(buffer, "BloodBathOnly", 0);
+ pEpisodeInfo->cutALevel = BloodINI->GetKeyInt(buffer, "CutSceneALevel", 0);
+ if (pEpisodeInfo->cutALevel > 0)
+ pEpisodeInfo->cutALevel--;
+ int j;
+ for (j = 0; j < kMaxLevels; j++)
+ {
+ LEVELINFO *pLevelInfo = &pEpisodeInfo->at28[j];
+ sprintf(buffer2, "Map%d", j+1);
+ if (!BloodINI->KeyExists(buffer, buffer2))
+ break;
+ const char *pMap = BloodINI->GetKeyString(buffer, buffer2, NULL);
+ CheckSectionAbend(pMap);
+ strncpy(pLevelInfo->at0, pMap, BMAX_PATH);
+ levelLoadMapInfo(BloodINI, pLevelInfo, pMap);
+ }
+ pEpisodeInfo->nLevels = j;
+ }
+ gEpisodeCount = i;
+ MenuSetupEpisodeInfo();
+}
+
+void levelAddUserMap(const char *pzMap)
+{
+ char buffer[BMAX_PATH];
+ //strcpy(buffer, g_modDir);
+ strncpy(buffer, pzMap, BMAX_PATH);
+ ChangeExtension(buffer, ".DEF");
+
+ IniFile UserINI(buffer);
+ int nEpisode = ClipRange(UserINI.GetKeyInt(NULL, "Episode", 0), 0, 5);
+ EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[nEpisode];
+ int nLevel = ClipRange(UserINI.GetKeyInt(NULL, "Level", pEpisodeInfo->nLevels), 0, 15);
+ if (nLevel >= pEpisodeInfo->nLevels)
+ {
+ if (pEpisodeInfo->nLevels == 0)
+ {
+ gEpisodeCount++;
+ sprintf(pEpisodeInfo->at0, "Episode %d", nEpisode);
+ }
+ nLevel = pEpisodeInfo->nLevels++;
+ }
+ LEVELINFO *pLevelInfo = &pEpisodeInfo->at28[nLevel];
+ ChangeExtension(buffer, "");
+ strncpy(pLevelInfo->at0, buffer, BMAX_PATH);
+ levelLoadMapInfo(&UserINI, pLevelInfo, NULL);
+ gGameOptions.nEpisode = nEpisode;
+ gGameOptions.nLevel = nLevel;
+ gGameOptions.uMapCRC = dbReadMapCRC(pLevelInfo->at0);
+ strcpy(gGameOptions.zLevelName, pLevelInfo->at0);
+ MenuSetupEpisodeInfo();
+}
+
+void levelGetNextLevels(int nEpisode, int nLevel, int *pnEndingA, int *pnEndingB)
+{
+ dassert(pnEndingA != NULL && pnEndingB != NULL);
+ LEVELINFO *pLevelInfo = &gEpisodeInfo[nEpisode].at28[nLevel];
+ int nEndingA = pLevelInfo->ate4;
+ if (nEndingA >= 0)
+ nEndingA--;
+ int nEndingB = pLevelInfo->ate8;
+ if (nEndingB >= 0)
+ nEndingB--;
+ *pnEndingA = nEndingA;
+ *pnEndingB = nEndingB;
+}
+
+void levelEndLevel(int arg)
+{
+ int nEndingA, nEndingB;
+ EPISODEINFO *pEpisodeInfo = &gEpisodeInfo[gGameOptions.nEpisode];
+ gGameOptions.uGameFlags |= 1;
+ levelGetNextLevels(gGameOptions.nEpisode, gGameOptions.nLevel, &nEndingA, &nEndingB);
+ switch (arg)
+ {
+ case 0:
+ if (nEndingA == -1)
+ {
+ if (pEpisodeInfo->at8f98[0])
+ gGameOptions.uGameFlags |= 8;
+ gGameOptions.nLevel = 0;
+ gGameOptions.uGameFlags |= 2;
+ }
+ else
+ gNextLevel = nEndingA;
+ break;
+ case 1:
+ if (nEndingB == -1)
+ {
+ if (gGameOptions.nEpisode + 1 < gEpisodeCount)
+ {
+ if (pEpisodeInfo->at8f98[0])
+ gGameOptions.uGameFlags |= 8;
+ gGameOptions.nLevel = 0;
+ gGameOptions.uGameFlags |= 2;
+ }
+ else
+ {
+ gGameOptions.nLevel = 0;
+ gGameOptions.uGameFlags |= 1;
+ }
+ }
+ else
+ gNextLevel = nEndingB;
+ break;
+ }
+}
+
+// By NoOne: this function can be called via sending numbered command to TX kGDXChannelEndLevel
+// This allows to set custom next level instead of taking it from INI file.
+void levelEndLevelCustom(int nLevel) {
+
+ gGameOptions.uGameFlags |= 1;
+
+ if (nLevel >= 16 || nLevel < 0)
+ {
+
+ gGameOptions.uGameFlags |= 2;
+ gGameOptions.nLevel = 0;
+ return;
+ }
+
+ gNextLevel = nLevel;
+}
+
+void levelRestart(void)
+{
+ levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
+ gStartNewGame = true;
+}
+
+int levelGetMusicIdx(const char *str)
+{
+ int32_t lev, ep;
+ signed char b1, b2;
+
+ int numMatches = sscanf(str, "%c%d%c%d", &b1, &ep, &b2, &lev);
+
+ if (numMatches != 4 || Btoupper(b1) != 'E' || Btoupper(b2) != 'L')
+ return -1;
+
+ if ((unsigned)--lev >= kMaxLevels || (unsigned)--ep >= kMaxEpisodes)
+ return -2;
+
+ return (ep * kMaxLevels) + lev;
+}
+
+bool levelTryPlayMusic(int nEpisode, int nLevel, bool bSetLevelSong)
+{
+ char buffer[BMAX_PATH];
+ if (CDAudioToggle && gEpisodeInfo[nEpisode].at28[nLevel].ate0 > 0)
+ snprintf(buffer, BMAX_PATH, "blood%02i.ogg", gEpisodeInfo[nEpisode].at28[nLevel].ate0);
+ else
+ strncpy(buffer, gEpisodeInfo[nEpisode].at28[nLevel].atd0, BMAX_PATH);
+ bool bReturn = !!sndPlaySong(buffer, true);
+ if (!bReturn || bSetLevelSong)
+ strncpy(gGameOptions.zLevelSong, buffer, BMAX_PATH);
+ return bReturn;
+}
+
+void levelTryPlayMusicOrNothing(int nEpisode, int nLevel)
+{
+ if (levelTryPlayMusic(nEpisode, nLevel, true))
+ sndStopSong();
+}
+
+class LevelsLoadSave : public LoadSave
+{
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+
+static LevelsLoadSave *myLoadSave;
+
+void LevelsLoadSave::Load(void)
+{
+ Read(&gNextLevel, sizeof(gNextLevel));
+ Read(&gGameOptions, sizeof(gGameOptions));
+ Read(&gGameStarted, sizeof(gGameStarted));
+}
+
+void LevelsLoadSave::Save(void)
+{
+ Write(&gNextLevel, sizeof(gNextLevel));
+ Write(&gGameOptions, sizeof(gGameOptions));
+ Write(&gGameStarted, sizeof(gGameStarted));
+}
+
+void LevelsLoadSaveConstruct(void)
+{
+ myLoadSave = new LevelsLoadSave();
+}
+
diff --git a/source/blood/src/levels.h b/source/blood/src/levels.h
new file mode 100644
index 000000000..306ffff9a
--- /dev/null
+++ b/source/blood/src/levels.h
@@ -0,0 +1,138 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "common_game.h"
+#include "inifile.h"
+
+#define kMaxMessages 32
+#define kMaxEpisodes 7
+#define kMaxLevels 16
+
+#pragma pack(push, 1)
+
+struct GAMEOPTIONS {
+ char nGameType;
+ char nDifficulty;
+ int nEpisode;
+ int nLevel;
+ char zLevelName[BMAX_PATH];
+ char zLevelSong[BMAX_PATH];
+ int nTrackNumber; //at12a;
+ char szSaveGameName[BMAX_PATH];
+ char szUserGameName[BMAX_PATH];
+ short nSaveGameSlot;
+ int picEntry;
+ unsigned int uMapCRC;
+ char nMonsterSettings;
+ int uGameFlags;
+ int uNetGameFlags;
+ char nWeaponSettings;
+ char nItemSettings;
+ char nRespawnSettings;
+ char nTeamSettings;
+ int nMonsterRespawnTime;
+ int nWeaponRespawnTime;
+ int nItemRespawnTime;
+ int nSpecialRespawnTime;
+};
+
+#pragma pack(pop)
+
+enum {
+ MUS_FIRST_SPECIAL = kMaxEpisodes*kMaxLevels,
+
+ MUS_INTRO = MUS_FIRST_SPECIAL,
+ MUS_LOADING = MUS_FIRST_SPECIAL + 1,
+};
+
+struct LEVELINFO
+{
+ char at0[BMAX_PATH]; // Filename
+ char at90[32]; // Title
+ char atb0[32]; // Author
+ char atd0[BMAX_PATH]; // Song;
+ int ate0; // SongId
+ int ate4; // EndingA
+ int ate8; // EndingB
+ char atec[kMaxMessages][64]; // Messages
+ char at8ec; // Fog
+ char at8ed; // Weather
+}; // 0x8ee bytes
+
+struct EPISODEINFO
+{
+ char at0[32];
+ int nLevels;
+ unsigned int bloodbath : 1;
+ unsigned int cutALevel : 4;
+ LEVELINFO at28[kMaxLevels];
+ char at8f08[BMAX_PATH];
+ char at8f98[BMAX_PATH];
+ int at9028;
+ int at902c;
+ char at9030[BMAX_PATH];
+ char at90c0[BMAX_PATH];
+};
+
+extern EPISODEINFO gEpisodeInfo[];
+extern GAMEOPTIONS gSingleGameOptions;
+extern GAMEOPTIONS gGameOptions;
+extern int gSkill;
+extern char BloodIniFile[];
+extern char BloodIniPre[];
+extern bool bINIOverride;
+extern int gEpisodeCount;
+extern int gNextLevel;
+extern bool gGameStarted;
+extern int gLevelTime;
+
+void levelInitINI(const char *pzIni);
+void levelOverrideINI(const char *pzIni);
+void levelPlayIntroScene(int nEpisode);
+void levelPlayEndScene(int nEpisode);
+void levelClearSecrets(void);
+void levelSetupSecret(int nCount);
+void levelTriggerSecret(int nSecret);
+void CheckSectionAbend(const char *pzSection);
+void CheckKeyAbend(const char *pzSection, const char *pzKey);
+LEVELINFO * levelGetInfoPtr(int nEpisode, int nLevel);
+char * levelGetFilename(int nEpisode, int nLevel);
+char * levelGetMessage(int nMessage);
+char * levelGetTitle(void);
+char * levelGetAuthor(void);
+void levelSetupOptions(int nEpisode, int nLevel);
+void levelLoadMapInfo(IniFile *pIni, LEVELINFO *pLevelInfo, const char *pzSection);
+void levelLoadDefaults(void);
+void levelAddUserMap(const char *pzMap);
+// EndingA is normal ending, EndingB is secret level
+void levelGetNextLevels(int nEpisode, int nLevel, int *pnEndingA, int *pnEndingB);
+// arg: 0 is normal exit, 1 is secret level
+void levelEndLevel(int arg);
+
+// By NoOne: custom level selection via numbered command which sent to TX ID 6.
+void levelEndLevelCustom(int nLevel);
+
+void levelRestart(void);
+int levelGetMusicIdx(const char *str);
+bool levelTryPlayMusic(int nEpisode, int nlevel, bool bSetLevelSong = false);
+void levelTryPlayMusicOrNothing(int nEpisode, int nLevel);
diff --git a/source/blood/src/loadsave.cpp b/source/blood/src/loadsave.cpp
new file mode 100644
index 000000000..ad08434b2
--- /dev/null
+++ b/source/blood/src/loadsave.cpp
@@ -0,0 +1,485 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include "build.h"
+#include "compat.h"
+#include "mmulti.h"
+#include "common_game.h"
+#include "config.h"
+#include "ai.h"
+#include "asound.h"
+#include "blood.h"
+#include "demo.h"
+#include "globals.h"
+#include "db.h"
+#include "messages.h"
+#include "menu.h"
+#include "network.h"
+#include "loadsave.h"
+#include "resource.h"
+#include "screen.h"
+#include "sectorfx.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "view.h"
+
+GAMEOPTIONS gSaveGameOptions[10];
+char *gSaveGamePic[10];
+unsigned int gSavedOffset = 0;
+
+unsigned int dword_27AA38 = 0;
+unsigned int dword_27AA3C = 0;
+unsigned int dword_27AA40 = 0;
+void *dword_27AA44 = NULL;
+
+LoadSave LoadSave::head(123);
+FILE *LoadSave::hSFile = NULL;
+int LoadSave::hLFile = -1;
+
+short word_27AA54 = 0;
+
+void sub_76FD4(void)
+{
+ if (!dword_27AA44)
+ dword_27AA44 = Resource::Alloc(0x186a0);
+}
+
+void LoadSave::Save(void)
+{
+ ThrowError("Pure virtual function called");
+}
+
+void LoadSave::Load(void)
+{
+ ThrowError("Pure virtual function called");
+}
+
+void LoadSave::Read(void *pData, int nSize)
+{
+ dword_27AA38 += nSize;
+ dassert(hLFile != -1);
+ if (kread(hLFile, pData, nSize) != nSize)
+ ThrowError("Error reading save file.");
+}
+
+void LoadSave::Write(void *pData, int nSize)
+{
+ dword_27AA38 += nSize;
+ dword_27AA3C += nSize;
+ dassert(hSFile != NULL);
+ if (fwrite(pData, 1, nSize, hSFile) != (size_t)nSize)
+ ThrowError("File error #%d writing save file.", errno);
+}
+
+void LoadSave::LoadGame(char *pzFile)
+{
+ bool demoWasPlayed = gDemo.at1;
+ if (gDemo.at1)
+ gDemo.Close();
+
+ sndKillAllSounds();
+ sfxKillAllSounds();
+ ambKillAll();
+ seqKillAll();
+ if (!gGameStarted)
+ {
+ memset(xsprite, 0, sizeof(xsprite));
+ memset(sprite, 0, sizeof(spritetype)*kMaxSprites);
+ automapping = 1;
+ }
+ hLFile = kopen4load(pzFile, 0);
+ if (hLFile == -1)
+ ThrowError("Error loading save file.");
+ LoadSave *rover = head.next;
+ while (rover != &head)
+ {
+ rover->Load();
+ rover = rover->next;
+ }
+ kclose(hLFile);
+ hLFile = -1;
+ if (!gGameStarted)
+ scrLoadPLUs();
+ InitSectorFX();
+ viewInitializePrediction();
+ PreloadCache();
+ if (!bVanilla && !gMe->packInfo[1].at0) // if diving suit is not active, turn off reverb sound effect
+ sfxSetReverb(0);
+ ambInit();
+ memset(myMinLag, 0, sizeof(myMinLag));
+ otherMinLag = 0;
+ myMaxLag = 0;
+ gNetFifoClock = 0;
+ gNetFifoTail = 0;
+ memset(gNetFifoHead, 0, sizeof(gNetFifoHead));
+ gPredictTail = 0;
+ gNetFifoMasterTail = 0;
+ memset(gFifoInput, 0, sizeof(gFifoInput));
+ memset(gChecksum, 0, sizeof(gChecksum));
+ memset(gCheckFifo, 0, sizeof(gCheckFifo));
+ memset(gCheckHead, 0, sizeof(gCheckHead));
+ gSendCheckTail = 0;
+ gCheckTail = 0;
+ gBufferJitter = 0;
+ bOutOfSync = 0;
+ for (int i = 0; i < gNetPlayers; i++)
+ playerSetRace(&gPlayer[i], gPlayer[i].at5f);
+ viewSetMessage("");
+ viewSetErrorMessage("");
+ if (!gGameStarted)
+ {
+ netWaitForEveryone(0);
+ memset(gPlayerReady, 0, sizeof(gPlayerReady));
+ }
+ gFrameTicks = 0;
+ gFrame = 0;
+ gCacheMiss = 0;
+ gFrameRate = 0;
+ gGameClock = 0;
+ gPaused = 0;
+ gGameStarted = 1;
+ bVanilla = false;
+
+ if (MusicRestartsOnLoadToggle
+ || demoWasPlayed
+ || (gMusicPrevLoadedEpisode != gGameOptions.nEpisode || gMusicPrevLoadedLevel != gGameOptions.nLevel))
+ {
+ levelTryPlayMusic(gGameOptions.nEpisode, gGameOptions.nLevel);
+ }
+ gMusicPrevLoadedEpisode = gGameOptions.nEpisode;
+ gMusicPrevLoadedLevel = gGameOptions.nLevel;
+
+ netBroadcastPlayerInfo(myconnectindex);
+ //sndPlaySong(gGameOptions.zLevelSong, 1);
+}
+
+void LoadSave::SaveGame(char *pzFile)
+{
+ hSFile = fopen(pzFile, "wb");
+ if (hSFile == NULL)
+ ThrowError("File error #%d creating save file.", errno);
+ dword_27AA38 = 0;
+ dword_27AA40 = 0;
+ LoadSave *rover = head.next;
+ while (rover != &head)
+ {
+ rover->Save();
+ if (dword_27AA38 > dword_27AA40)
+ dword_27AA40 = dword_27AA38;
+ dword_27AA38 = 0;
+ rover = rover->next;
+ }
+ fclose(hSFile);
+ hSFile = NULL;
+}
+
+class MyLoadSave : public LoadSave
+{
+public:
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+void MyLoadSave::Load(void)
+{
+ psky_t *pSky = tileSetupSky(0);
+ int id;
+ Read(&id, sizeof(id));
+ if (id != 0x5653424e/*'VSBN'*/)
+ ThrowError("Old saved game found");
+ short version;
+ Read(&version, sizeof(version));
+ if (version != BYTEVERSION)
+ ThrowError("Incompatible version of saved game found!");
+ Read(&gGameOptions, sizeof(gGameOptions));
+ Read(&numsectors, sizeof(numsectors));
+ Read(&numwalls, sizeof(numwalls));
+ Read(&numsectors, sizeof(numsectors));
+ int nNumSprites;
+ Read(&nNumSprites, sizeof(nNumSprites));
+ memset(sector, 0, sizeof(sector[0])*kMaxSectors);
+ memset(wall, 0, sizeof(wall[0])*kMaxWalls);
+ memset(sprite, 0, sizeof(sprite[0])*kMaxSprites);
+ Read(sector, sizeof(sector[0])*numsectors);
+ Read(wall, sizeof(wall[0])*numwalls);
+ Read(sprite, sizeof(sprite[0])*kMaxSprites);
+ Read(qsector_filler, sizeof(qsector_filler[0])*numsectors);
+ Read(qsprite_filler, sizeof(qsprite_filler[0])*kMaxSprites);
+ Read(&randomseed, sizeof(randomseed));
+ Read(¶llaxtype, sizeof(parallaxtype));
+ Read(&showinvisibility, sizeof(showinvisibility));
+ Read(&pSky->horizfrac, sizeof(pSky->horizfrac));
+ Read(&pSky->yoffs, sizeof(pSky->yoffs));
+ Read(&pSky->yscale, sizeof(pSky->yscale));
+ Read(&gVisibility, sizeof(gVisibility));
+ Read(&g_visibility, sizeof(g_visibility));
+ Read(¶llaxvisibility, sizeof(parallaxvisibility));
+ Read(pSky->tileofs, sizeof(pSky->tileofs));
+ Read(&pSky->lognumtiles, sizeof(pSky->lognumtiles));
+ Read(headspritesect, sizeof(headspritesect));
+ Read(headspritestat, sizeof(headspritestat));
+ Read(prevspritesect, sizeof(prevspritesect));
+ Read(prevspritestat, sizeof(prevspritestat));
+ Read(nextspritesect, sizeof(nextspritesect));
+ Read(nextspritestat, sizeof(nextspritestat));
+ Read(show2dsector, sizeof(show2dsector));
+ Read(show2dwall, sizeof(show2dwall));
+ Read(show2dsprite, sizeof(show2dsprite));
+ Read(&automapping, sizeof(automapping));
+ Read(gotpic, sizeof(gotpic));
+ Read(gotsector, sizeof(gotsector));
+ Read(&gFrameClock, sizeof(gFrameClock));
+ Read(&gFrameTicks, sizeof(gFrameTicks));
+ Read(&gFrame, sizeof(gFrame));
+ int nGameClock;
+ Read(&nGameClock, sizeof(nGameClock));
+ gGameClock = nGameClock;
+ Read(&gLevelTime, sizeof(gLevelTime));
+ Read(&gPaused, sizeof(gPaused));
+ Read(&gbAdultContent, sizeof(gbAdultContent));
+ Read(baseWall, sizeof(baseWall[0])*numwalls);
+ Read(baseSprite, sizeof(baseSprite[0])*nNumSprites);
+ Read(baseFloor, sizeof(baseFloor[0])*numsectors);
+ Read(baseCeil, sizeof(baseCeil[0])*numsectors);
+ Read(velFloor, sizeof(velFloor[0])*numsectors);
+ Read(velCeil, sizeof(velCeil[0])*numsectors);
+ Read(&gHitInfo, sizeof(gHitInfo));
+ Read(&byte_1A76C6, sizeof(byte_1A76C6));
+ Read(&byte_1A76C8, sizeof(byte_1A76C8));
+ Read(&byte_1A76C7, sizeof(byte_1A76C7));
+ Read(&byte_19AE44, sizeof(byte_19AE44));
+ Read(gStatCount, sizeof(gStatCount));
+ Read(nextXSprite, sizeof(nextXSprite));
+ Read(nextXWall, sizeof(nextXWall));
+ Read(nextXSector, sizeof(nextXSector));
+ memset(xsprite, 0, sizeof(xsprite));
+ for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
+ {
+ if (sprite[nSprite].statnum < kMaxStatus)
+ {
+ int nXSprite = sprite[nSprite].extra;
+ if (nXSprite > 0)
+ Read(&xsprite[nXSprite], sizeof(XSPRITE));
+ }
+ }
+ memset(xwall, 0, sizeof(xwall));
+ for (int nWall = 0; nWall < numwalls; nWall++)
+ {
+ int nXWall = wall[nWall].extra;
+ if (nXWall > 0)
+ Read(&xwall[nXWall], sizeof(XWALL));
+ }
+ memset(xsector, 0, sizeof(xsector));
+ for (int nSector = 0; nSector < numsectors; nSector++)
+ {
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0)
+ Read(&xsector[nXSector], sizeof(XSECTOR));
+ }
+ Read(xvel, nNumSprites*sizeof(xvel[0]));
+ Read(yvel, nNumSprites*sizeof(yvel[0]));
+ Read(zvel, nNumSprites*sizeof(zvel[0]));
+ Read(&gMapRev, sizeof(gMapRev));
+ Read(&gSongId, sizeof(gSkyCount));
+ Read(&gFogMode, sizeof(gFogMode));
+ gCheatMgr.sub_5BCF4();
+}
+
+void MyLoadSave::Save(void)
+{
+ psky_t *pSky = tileSetupSky(0);
+ int nNumSprites = 0;
+ int id = 0x5653424e/*'VSBN'*/;
+ Write(&id, sizeof(id));
+ short version = BYTEVERSION;
+ Write(&version, sizeof(version));
+ for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
+ {
+ if (sprite[nSprite].statnum < kMaxStatus && nSprite > nNumSprites)
+ nNumSprites = nSprite;
+ }
+ //nNumSprites += 2;
+ nNumSprites++;
+ Write(&gGameOptions, sizeof(gGameOptions));
+ Write(&numsectors, sizeof(numsectors));
+ Write(&numwalls, sizeof(numwalls));
+ Write(&numsectors, sizeof(numsectors));
+ Write(&nNumSprites, sizeof(nNumSprites));
+ Write(sector, sizeof(sector[0])*numsectors);
+ Write(wall, sizeof(wall[0])*numwalls);
+ Write(sprite, sizeof(sprite[0])*kMaxSprites);
+ Write(qsector_filler, sizeof(qsector_filler[0])*numsectors);
+ Write(qsprite_filler, sizeof(qsprite_filler[0])*kMaxSprites);
+ Write(&randomseed, sizeof(randomseed));
+ Write(¶llaxtype, sizeof(parallaxtype));
+ Write(&showinvisibility, sizeof(showinvisibility));
+ Write(&pSky->horizfrac, sizeof(pSky->horizfrac));
+ Write(&pSky->yoffs, sizeof(pSky->yoffs));
+ Write(&pSky->yscale, sizeof(pSky->yscale));
+ Write(&gVisibility, sizeof(gVisibility));
+ Write(&g_visibility, sizeof(g_visibility));
+ Write(¶llaxvisibility, sizeof(parallaxvisibility));
+ Write(pSky->tileofs, sizeof(pSky->tileofs));
+ Write(&pSky->lognumtiles, sizeof(pSky->lognumtiles));
+ Write(headspritesect, sizeof(headspritesect));
+ Write(headspritestat, sizeof(headspritestat));
+ Write(prevspritesect, sizeof(prevspritesect));
+ Write(prevspritestat, sizeof(prevspritestat));
+ Write(nextspritesect, sizeof(nextspritesect));
+ Write(nextspritestat, sizeof(nextspritestat));
+ Write(show2dsector, sizeof(show2dsector));
+ Write(show2dwall, sizeof(show2dwall));
+ Write(show2dsprite, sizeof(show2dsprite));
+ Write(&automapping, sizeof(automapping));
+ Write(gotpic, sizeof(gotpic));
+ Write(gotsector, sizeof(gotsector));
+ Write(&gFrameClock, sizeof(gFrameClock));
+ Write(&gFrameTicks, sizeof(gFrameTicks));
+ Write(&gFrame, sizeof(gFrame));
+ int nGameClock = gGameClock;
+ Write(&nGameClock, sizeof(nGameClock));
+ Write(&gLevelTime, sizeof(gLevelTime));
+ Write(&gPaused, sizeof(gPaused));
+ Write(&gbAdultContent, sizeof(gbAdultContent));
+ Write(baseWall, sizeof(baseWall[0])*numwalls);
+ Write(baseSprite, sizeof(baseSprite[0])*nNumSprites);
+ Write(baseFloor, sizeof(baseFloor[0])*numsectors);
+ Write(baseCeil, sizeof(baseCeil[0])*numsectors);
+ Write(velFloor, sizeof(velFloor[0])*numsectors);
+ Write(velCeil, sizeof(velCeil[0])*numsectors);
+ Write(&gHitInfo, sizeof(gHitInfo));
+ Write(&byte_1A76C6, sizeof(byte_1A76C6));
+ Write(&byte_1A76C8, sizeof(byte_1A76C8));
+ Write(&byte_1A76C7, sizeof(byte_1A76C7));
+ Write(&byte_19AE44, sizeof(byte_19AE44));
+ Write(gStatCount, sizeof(gStatCount));
+ Write(nextXSprite, sizeof(nextXSprite));
+ Write(nextXWall, sizeof(nextXWall));
+ Write(nextXSector, sizeof(nextXSector));
+ for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
+ {
+ if (sprite[nSprite].statnum < kMaxStatus)
+ {
+ int nXSprite = sprite[nSprite].extra;
+ if (nXSprite > 0)
+ Write(&xsprite[nXSprite], sizeof(XSPRITE));
+ }
+ }
+ for (int nWall = 0; nWall < numwalls; nWall++)
+ {
+ int nXWall = wall[nWall].extra;
+ if (nXWall > 0)
+ Write(&xwall[nXWall], sizeof(XWALL));
+ }
+ for (int nSector = 0; nSector < numsectors; nSector++)
+ {
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0)
+ Write(&xsector[nXSector], sizeof(XSECTOR));
+ }
+ Write(xvel, nNumSprites*sizeof(xvel[0]));
+ Write(yvel, nNumSprites*sizeof(yvel[0]));
+ Write(zvel, nNumSprites*sizeof(zvel[0]));
+ Write(&gMapRev, sizeof(gMapRev));
+ Write(&gSongId, sizeof(gSkyCount));
+ Write(&gFogMode, sizeof(gFogMode));
+}
+
+void LoadSavedInfo(void)
+{
+ auto pList = klistpath("./", "game*.sav", CACHE1D_FIND_FILE);
+ int nCount = 0;
+ for (auto pIterator = pList; pIterator != NULL && nCount < 10; pIterator = pIterator->next, nCount++)
+ {
+ int hFile = kopen4loadfrommod(pIterator->name, 0);
+ if (hFile == -1)
+ ThrowError("Error loading save file header.");
+ int vc;
+ short v4;
+ vc = 0;
+ v4 = word_27AA54;
+ if ((uint32_t)kread(hFile, &vc, sizeof(vc)) != sizeof(vc))
+ {
+ kclose(hFile);
+ continue;
+ }
+ if (vc != 0x5653424e/*'VSBN'*/)
+ {
+ kclose(hFile);
+ continue;
+ }
+ kread(hFile, &v4, sizeof(v4));
+ if (v4 != BYTEVERSION)
+ {
+ kclose(hFile);
+ continue;
+ }
+ if ((uint32_t)kread(hFile, &gSaveGameOptions[nCount], sizeof(gSaveGameOptions[0])) != sizeof(gSaveGameOptions[0]))
+ ThrowError("Error reading save file.");
+ strcpy(strRestoreGameStrings[gSaveGameOptions[nCount].nSaveGameSlot], gSaveGameOptions[nCount].szUserGameName);
+ kclose(hFile);
+ }
+ klistfree(pList);
+}
+
+void UpdateSavedInfo(int nSlot)
+{
+ strcpy(strRestoreGameStrings[gSaveGameOptions[nSlot].nSaveGameSlot], gSaveGameOptions[nSlot].szUserGameName);
+}
+
+static MyLoadSave *myLoadSave;
+
+
+void LoadSaveSetup(void)
+{
+ void ActorLoadSaveConstruct(void);
+ void AILoadSaveConstruct(void);
+ void EndGameLoadSaveConstruct(void);
+ void EventQLoadSaveConstruct(void);
+ void LevelsLoadSaveConstruct(void);
+ void MessagesLoadSaveConstruct(void);
+ void MirrorLoadSaveConstruct(void);
+ void PlayerLoadSaveConstruct(void);
+ void SeqLoadSaveConstruct(void);
+ void TriggersLoadSaveConstruct(void);
+ void ViewLoadSaveConstruct(void);
+ void WarpLoadSaveConstruct(void);
+ void WeaponLoadSaveConstruct(void);
+
+ myLoadSave = new MyLoadSave();
+
+ ActorLoadSaveConstruct();
+ AILoadSaveConstruct();
+ EndGameLoadSaveConstruct();
+ EventQLoadSaveConstruct();
+ LevelsLoadSaveConstruct();
+ MessagesLoadSaveConstruct();
+ MirrorLoadSaveConstruct();
+ PlayerLoadSaveConstruct();
+ SeqLoadSaveConstruct();
+ TriggersLoadSaveConstruct();
+ ViewLoadSaveConstruct();
+ WarpLoadSaveConstruct();
+ WeaponLoadSaveConstruct();
+}
\ No newline at end of file
diff --git a/source/blood/src/loadsave.h b/source/blood/src/loadsave.h
new file mode 100644
index 000000000..0f8c79c57
--- /dev/null
+++ b/source/blood/src/loadsave.h
@@ -0,0 +1,60 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include
+#include "levels.h"
+
+class LoadSave {
+public:
+ static LoadSave head;
+ static FILE *hSFile;
+ static int hLFile;
+ LoadSave *prev;
+ LoadSave *next;
+ LoadSave() {
+ prev = head.prev;
+ prev->next = this;
+ next = &head;
+ next->prev = this;
+ }
+ LoadSave(int dummy)
+ {
+ UNREFERENCED_PARAMETER(dummy);
+ next = prev = this;
+ }
+ //~LoadSave() { }
+ virtual void Save(void);
+ virtual void Load(void);
+ void Read(void *, int);
+ void Write(void *, int);
+ static void LoadGame(char *);
+ static void SaveGame(char *);
+};
+
+extern unsigned int gSavedOffset;
+extern GAMEOPTIONS gSaveGameOptions[];
+extern char *gSaveGamePic[10];
+
+void UpdateSavedInfo(int nSlot);
+void LoadSavedInfo(void);
+void LoadSaveSetup(void);
diff --git a/source/blood/src/map2d.cpp b/source/blood/src/map2d.cpp
new file mode 100644
index 000000000..7e6d89fe7
--- /dev/null
+++ b/source/blood/src/map2d.cpp
@@ -0,0 +1,255 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "mmulti.h"
+#include "common_game.h"
+#include "levels.h"
+#include "map2d.h"
+#include "trig.h"
+#include "view.h"
+
+char byte_27AACB;
+
+void sub_2541C(int x, int y, int z, short a)
+{
+ int tmpydim = (xdim * 5) / 8;
+ renderSetAspect(65536, divscale16(tmpydim * 320, xdim * 200));
+ int nCos = z*sintable[(0-a)&2047];
+ int nSin = z*sintable[(1536-a)&2047];
+ int nCos2 = mulscale16(nCos, yxaspect);
+ int nSin2 = mulscale16(nSin, yxaspect);
+ for (int i = 0; i < numsectors; i++)
+ {
+ if (byte_27AACB || (show2dsector[i>>3]&(1<<(i&7))))
+ {
+ int nStartWall = sector[i].wallptr;
+ int nEndWall = nStartWall+sector[i].wallnum;
+ int nZCeil = sector[i].ceilingz;
+ int nZFloor = sector[i].floorz;
+ walltype *pWall = &wall[nStartWall];
+ for (int j = nStartWall; j < nEndWall; j++, pWall++)
+ {
+ int nNextWall = pWall->nextwall;
+ if (nNextWall < 0)
+ continue;
+ if (sector[pWall->nextsector].ceilingz == nZCeil && sector[pWall->nextsector].floorz == nZFloor
+ && ((wall[nNextWall].cstat | pWall->cstat) & 0x30) == 0)
+ continue;
+ if (byte_27AACB)
+ continue;
+ if (show2dsector[pWall->nextsector>>3]&(1<<(pWall->nextsector&7)))
+ continue;
+ int wx = pWall->x-x;
+ int wy = pWall->y-y;
+ int cx = xdim<<11;
+ int x1 = cx+dmulscale16(wx, nCos, -wy, nSin);
+ int cy = ydim<<11;
+ int y1 = cy+dmulscale16(wy, nCos2, wx, nSin2);
+ walltype *pWall2 = &wall[pWall->point2];
+ wx = pWall2->x-x;
+ wy = pWall2->y-y;
+ int x2 = cx+dmulscale16(wx, nCos, -wy, nSin);
+ int y2 = cy+dmulscale16(wy, nCos2, wx, nSin2);
+ renderDrawLine(x1,y1,x2,y2,24);
+ }
+ }
+ }
+ int nPSprite = gView->pSprite->index;
+ for (int i = 0; i < numsectors; i++)
+ {
+ if (byte_27AACB || (show2dsector[i>>3]&(1<<(i&7))))
+ {
+ for (int nSprite = headspritesect[i]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (nSprite == nPSprite)
+ continue;
+ if (pSprite->cstat&32768)
+ continue;
+ }
+ }
+ }
+ for (int i = 0; i < numsectors; i++)
+ {
+ if (byte_27AACB || (show2dsector[i>>3]&(1<<(i&7))))
+ {
+ int nStartWall = sector[i].wallptr;
+ int nEndWall = nStartWall+sector[i].wallnum;
+ walltype *pWall = &wall[nStartWall];
+ int nNWall = -1;
+ int x1, y1, x2 = 0, y2 = 0;
+ for (int j = nStartWall; j < nEndWall; j++, pWall++)
+ {
+ int nNextWall = pWall->nextwall;
+ if (nNextWall >= 0)
+ continue;
+ if (!tilesiz[pWall->picnum].x || !tilesiz[pWall->picnum].y)
+ continue;
+ if (nNWall == j)
+ {
+ x1 = x2;
+ y1 = y2;
+ }
+ else
+ {
+ int wx = pWall->x-x;
+ int wy = pWall->y-y;
+ x1 = (xdim<<11)+dmulscale16(wx, nCos, -wy, nSin);
+ y1 = (ydim<<11)+dmulscale16(wy, nCos2, wx, nSin2);
+ }
+ nNWall = pWall->point2;
+ walltype *pWall2 = &wall[nNWall];
+ int wx = pWall2->x-x;
+ int wy = pWall2->y-y;
+ x2 = (xdim<<11)+dmulscale16(wx, nCos, -wy, nSin);
+ y2 = (ydim<<11)+dmulscale16(wy, nCos2, wx, nSin2);
+ renderDrawLine(x1,y1,x2,y2,24);
+ }
+ }
+ }
+ videoSetCorrectedAspect();
+
+ for (int i = connecthead; i >= 0; i = connectpoint2[i])
+ {
+ if (gViewMap.bFollowMode || gView->at57 != i)
+ {
+ PLAYER *pPlayer = &gPlayer[i];
+ spritetype *pSprite = pPlayer->pSprite;
+ int px = pSprite->x-x;
+ int py = pSprite->y-y;
+ int pa = (pSprite->ang-a)&2047;
+ if (i == gView->at57)
+ {
+ px = 0;
+ py = 0;
+ pa = 0;
+ }
+ int x1 = dmulscale16(px, nCos, -py, nSin);
+ int y1 = dmulscale16(py, nCos2, px, nSin2);
+ if (i == gView->at57 || gGameOptions.nGameType == 1)
+ {
+ int nTile = pSprite->picnum;
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, (pSprite->clipdist<<2)+16, 0x13001);
+ int nTop, nBottom;
+ GetSpriteExtents(pSprite, &nTop, &nBottom);
+ int nScale = mulscale((pSprite->yrepeat+((floorZ-nBottom)>>8))*z, yxaspect, 16);
+ nScale = ClipRange(nScale, 8000, 65536<<1);
+ rotatesprite((xdim<<15)+(x1<<4), (ydim<<15)+(y1<<4), nScale, pa, nTile, pSprite->shade, pSprite->pal, (pSprite->cstat&2)>>1,
+ windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
+ }
+ }
+ }
+}
+
+CViewMap::CViewMap()
+{
+ bActive = 0;
+}
+
+void CViewMap::sub_25C38(int _x, int _y, int _angle, short zoom, char unk1)
+{
+ bActive = 1;
+ x = _x;
+ y = _y;
+ angle = _angle;
+ nZoom = zoom;
+ FollowMode(unk1);
+ forward = 0;
+ turn = 0;
+ strafe = 0;
+}
+
+void CViewMap::sub_25C74(void)
+{
+ char pBuffer[128];
+ if (!bActive)
+ return;
+ char tm = 0;
+ int viewSize = gViewSize;
+ if (gViewSize > 3)
+ {
+ viewResizeView(3);
+ tm = 1;
+ }
+ videoClearScreen(0);
+ renderDrawMapView(x,y,nZoom>>2,angle);
+ sub_2541C(x,y,nZoom>>2,angle);
+ char *pTitle = levelGetTitle();
+ char *pFilename = levelGetFilename(gGameOptions.nEpisode, gGameOptions.nLevel);
+ if (pTitle)
+ sprintf(pBuffer, "%s: %s", pFilename, pTitle);
+ else
+ sprintf(pBuffer, "%s", pFilename);
+ int nViewY;
+ if (gViewSize > 3)
+ nViewY = gViewY1S-16;
+ else
+ nViewY = gViewY0S+1;
+ viewDrawText(3, pBuffer, gViewX1S, nViewY, -128, 0, 2, 0, 256);
+
+ if (gViewMap.bFollowMode)
+ viewDrawText(3, "MAP FOLLOW MODE", gViewX1S, nViewY+8, -128, 0, 2, 0, 256);
+ else
+ viewDrawText(3, "MAP SCROLL MODE", gViewX1S, nViewY+8, -128, 0, 2, 0, 256);
+ if (tm)
+ viewResizeView(viewSize);
+}
+
+void CViewMap::sub_25DB0(spritetype *pSprite)
+{
+ nZoom = gZoom;
+ if (bFollowMode)
+ {
+ x = pSprite->x;
+ y = pSprite->y;
+ angle = pSprite->ang;
+ }
+ else
+ {
+ angle += turn>>3;
+ x += mulscale24(forward, Cos(angle));
+ y += mulscale24(forward, Sin(angle));
+ x -= mulscale24(strafe, Cos(angle+512));
+ y -= mulscale24(strafe, Sin(angle+512));
+ forward = 0;
+ strafe = 0;
+ turn = 0;
+ }
+ sub_25C74();
+}
+
+void CViewMap::sub_25E84(int *_x, int *_y)
+{
+ if (_x)
+ *_x = x;
+ if (_y)
+ *_y = y;
+}
+
+void CViewMap::FollowMode(char mode)
+{
+ bFollowMode = mode;
+}
+
+CViewMap gViewMap;
diff --git a/source/blood/src/map2d.h b/source/blood/src/map2d.h
new file mode 100644
index 000000000..b74c819c8
--- /dev/null
+++ b/source/blood/src/map2d.h
@@ -0,0 +1,41 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "common_game.h"
+#include "db.h"
+class CViewMap {
+public:
+ char bActive;
+ int x, y, nZoom;
+ short angle;
+ char bFollowMode;
+ int forward, turn, strafe;
+ CViewMap();
+ void sub_25C38(int, int, int, short, char);
+ void sub_25C74(void);
+ void sub_25DB0(spritetype *pSprite);
+ void sub_25E84(int *, int*);
+ void FollowMode(char);
+};
+
+extern CViewMap gViewMap;
diff --git a/source/blood/src/menu.cpp b/source/blood/src/menu.cpp
new file mode 100644
index 000000000..76d7a9e05
--- /dev/null
+++ b/source/blood/src/menu.cpp
@@ -0,0 +1,2249 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "mmulti.h"
+#include "common_game.h"
+#include "fx_man.h"
+#include "music.h"
+#include "blood.h"
+#include "demo.h"
+#include "config.h"
+#include "gamemenu.h"
+#include "globals.h"
+#include "loadsave.h"
+#include "menu.h"
+#include "messages.h"
+#include "network.h"
+#include "osdcmds.h"
+#include "sfx.h"
+#include "screen.h"
+#include "sound.h"
+#include "view.h"
+
+void SaveGame(CGameMenuItemZEditBitmap *, CGameMenuEvent *);
+
+void SaveGameProcess(CGameMenuItemChain *);
+void SetDifficultyAndStart(CGameMenuItemChain *);
+void SetDetail(CGameMenuItemSlider *);
+void SetGamma(CGameMenuItemSlider *);
+void SetMusicVol(CGameMenuItemSlider *);
+void SetSoundVol(CGameMenuItemSlider *);
+void SetCDVol(CGameMenuItemSlider *);
+void SetDoppler(CGameMenuItemZBool *);
+void SetCrosshair(CGameMenuItemZBool *);
+void SetCenterHoriz(CGameMenuItemZBool *);
+void SetShowWeapons(CGameMenuItemZBool *);
+void SetSlopeTilting(CGameMenuItemZBool *);
+void SetViewBobbing(CGameMenuItemZBool *);
+void SetViewSwaying(CGameMenuItemZBool *);
+void SetMouseSensitivity(CGameMenuItemSliderFloat *);
+void SetMouseAimFlipped(CGameMenuItemZBool *);
+void SetTurnSpeed(CGameMenuItemSlider *);
+void ResetKeys(CGameMenuItemChain *);
+void ResetKeysClassic(CGameMenuItemChain *);
+void SetMessages(CGameMenuItemZBool *);
+void LoadGame(CGameMenuItemZEditBitmap *, CGameMenuEvent *);
+void SetupNetLevels(CGameMenuItemZCycle *);
+void StartNetGame(CGameMenuItemChain *);
+void SetParentalLock(CGameMenuItemZBool *);
+void TenProcess(CGameMenuItem7EA1C *);
+void SetupLevelMenuItem(int);
+void SetupVideoModeMenu(CGameMenuItemChain *);
+void SetVideoMode(CGameMenuItemChain *);
+void SetWidescreen(CGameMenuItemZBool *);
+void SetFOV(CGameMenuItemSlider *);
+void UpdateVideoModeMenuFrameLimit(CGameMenuItemZCycle *pItem);
+void UpdateVideoModeMenuFPSOffset(CGameMenuItemSlider *pItem);
+void UpdateVideoColorMenu(CGameMenuItemSliderFloat *);
+void ResetVideoColor(CGameMenuItemChain *);
+#ifdef USE_OPENGL
+void SetupVideoPolymostMenu(CGameMenuItemChain *);
+#endif
+
+char strRestoreGameStrings[][16] =
+{
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+};
+
+const char *zNetGameTypes[] =
+{
+ "Cooperative",
+ "Bloodbath",
+ "Teams",
+};
+
+const char *zMonsterStrings[] =
+{
+ "None",
+ "Bring 'em on",
+ "Respawn",
+};
+
+const char *zWeaponStrings[] =
+{
+ "Do not Respawn",
+ "Are Permanent",
+ "Respawn",
+ "Respawn with Markers",
+};
+
+const char *zItemStrings[] =
+{
+ "Do not Respawn",
+ "Respawn",
+ "Respawn with Markers",
+};
+
+const char *zRespawnStrings[] =
+{
+ "At Random Locations",
+ "Close to Weapons",
+ "Away from Enemies",
+};
+
+const char *zDiffStrings[] =
+{
+ "STILL KICKING",
+ "PINK ON THE INSIDE",
+ "LIGHTLY BROILED",
+ "WELL DONE",
+ "EXTRA CRISPY",
+};
+
+char zUserMapName[16];
+const char *zEpisodeNames[6];
+const char *zLevelNames[6][16];
+
+static char MenuGameFuncs[NUMGAMEFUNCTIONS][MAXGAMEFUNCLEN];
+static char const *MenuGameFuncNone = " -None-";
+static char const *pzGamefuncsStrings[NUMGAMEFUNCTIONS + 1];
+static int nGamefuncsValues[NUMGAMEFUNCTIONS + 1];
+static int nGamefuncsNum;
+
+CGameMenu menuMain;
+CGameMenu menuMainWithSave;
+CGameMenu menuNetMain;
+CGameMenu menuNetStart;
+CGameMenu menuEpisode;
+CGameMenu menuDifficulty;
+CGameMenu menuOptionsOld;
+CGameMenu menuControls;
+CGameMenu menuMessages;
+CGameMenu menuKeys;
+CGameMenu menuSaveGame;
+CGameMenu menuLoadGame;
+CGameMenu menuLoading;
+CGameMenu menuSounds;
+CGameMenu menuQuit;
+CGameMenu menuRestart;
+CGameMenu menuCredits;
+CGameMenu menuOrder;
+CGameMenu menuPlayOnline;
+CGameMenu menuParentalLock;
+CGameMenu menuSorry;
+CGameMenu menuSorry2;
+CGameMenu menuNetwork;
+CGameMenu menuNetworkHost;
+CGameMenu menuNetworkJoin;
+
+CGameMenuItemQAV itemBloodQAV("", 3, 160, 100, "BDRIP", true);
+CGameMenuItemQAV itemCreditsQAV("", 3, 160, 100, "CREDITS", false, true);
+CGameMenuItemQAV itemHelp3QAV("", 3, 160, 100, "HELP3", false, false);
+CGameMenuItemQAV itemHelp3BQAV("", 3, 160, 100, "HELP3B", false, false);
+CGameMenuItemQAV itemHelp4QAV("", 3, 160, 100, "HELP4", false, true);
+CGameMenuItemQAV itemHelp5QAV("", 3, 160, 100, "HELP5", false, true);
+
+CGameMenuItemTitle itemMainTitle("BLOOD", 1, 160, 20, 2038);
+CGameMenuItemChain itemMain1("NEW GAME", 1, 0, 45, 320, 1, &menuEpisode, -1, NULL, 0);
+//CGameMenuItemChain itemMain2("PLAY ONLINE", 1, 0, 65, 320, 1, &menuPlayOnline, -1, NULL, 0);
+CGameMenuItemChain itemMain2("MULTIPLAYER", 1, 0, 65, 320, 1, &menuNetwork, -1, NULL, 0);
+CGameMenuItemChain itemMain3("OPTIONS", 1, 0, 85, 320, 1, &menuOptions, -1, NULL, 0);
+CGameMenuItemChain itemMain4("LOAD GAME", 1, 0, 105, 320, 1, &menuLoadGame, -1, NULL, 0);
+CGameMenuItemChain itemMain5("HELP", 1, 0, 125, 320, 1, &menuOrder, -1, NULL, 0);
+CGameMenuItemChain itemMain6("CREDITS", 1, 0, 145, 320, 1, &menuCredits, -1, NULL, 0);
+CGameMenuItemChain itemMain7("QUIT", 1, 0, 165, 320, 1, &menuQuit, -1, NULL, 0);
+
+CGameMenuItemTitle itemMainSaveTitle("BLOOD", 1, 160, 20, 2038);
+CGameMenuItemChain itemMainSave1("NEW GAME", 1, 0, 45, 320, 1, &menuEpisode, -1, NULL, 0);
+//CGameMenuItemChain itemMainSave2("PLAY ONLINE", 1, 0, 60, 320, 1, &menuPlayOnline, -1, NULL, 0);
+CGameMenuItemChain itemMainSave2("OPTIONS", 1, 0, 60, 320, 1, &menuOptions, -1, NULL, 0);
+CGameMenuItemChain itemMainSave3("SAVE GAME", 1, 0, 75, 320, 1, &menuSaveGame, -1, SaveGameProcess, 0);
+CGameMenuItemChain itemMainSave4("LOAD GAME", 1, 0, 90, 320, 1, &menuLoadGame, -1, NULL, 0);
+CGameMenuItemChain itemMainSave5("HELP", 1, 0, 105, 320, 1, &menuOrder, -1, NULL, 0);
+CGameMenuItemChain itemMainSave6("CREDITS", 1, 0, 120, 320, 1, &menuCredits, -1, NULL, 0);
+CGameMenuItemChain itemMainSave7("END GAME", 1, 0, 135, 320, 1, &menuRestart, -1, NULL, 0);
+CGameMenuItemChain itemMainSave8("QUIT", 1, 0, 150, 320, 1, &menuQuit, -1, NULL, 0);
+
+CGameMenuItemTitle itemEpisodesTitle("EPISODES", 1, 160, 20, 2038);
+CGameMenuItemChain7F2F0 itemEpisodes[6];
+
+CGameMenuItemTitle itemDifficultyTitle("DIFFICULTY", 1, 160, 20, 2038);
+CGameMenuItemChain itemDifficulty1("STILL KICKING", 1, 0, 60, 320, 1, NULL, -1, SetDifficultyAndStart, 0);
+CGameMenuItemChain itemDifficulty2("PINK ON THE INSIDE", 1, 0, 80, 320, 1, NULL, -1, SetDifficultyAndStart, 1);
+CGameMenuItemChain itemDifficulty3("LIGHTLY BROILED", 1, 0, 100, 320, 1, NULL, -1, SetDifficultyAndStart, 2);
+CGameMenuItemChain itemDifficulty4("WELL DONE", 1, 0, 120, 320, 1, NULL, -1, SetDifficultyAndStart, 3);
+CGameMenuItemChain itemDifficulty5("EXTRA CRISPY", 1, 0, 140, 320, 1, 0, -1, SetDifficultyAndStart, 4);
+
+CGameMenuItemTitle itemOptionsOldTitle("OPTIONS", 1, 160, 20, 2038);
+CGameMenuItemChain itemOption1("CONTROLS...", 3, 0, 40, 320, 1, &menuControls, -1, NULL, 0);
+CGameMenuItemSlider sliderDetail("DETAIL:", 3, 66, 50, 180, gDetail, 0, 4, 1, SetDetail, -1, -1);
+CGameMenuItemSlider sliderGamma("GAMMA:", 3, 66, 60, 180, gGamma, 0, 15, 2, SetGamma, -1, -1);
+CGameMenuItemSlider sliderMusic("MUSIC:", 3, 66, 70, 180, MusicVolume, 0, 256, 48, SetMusicVol, -1, -1);
+CGameMenuItemSlider sliderSound("SOUND:", 3, 66, 80, 180, FXVolume, 0, 256, 48, SetSoundVol, -1, -1);
+CGameMenuItemSlider sliderCDAudio("CD AUDIO:", 3, 66, 90, 180, CDVolume, 0, 256, 48, SetCDVol, -1, -1);
+CGameMenuItemZBool bool3DAudio("3D AUDIO:", 3, 66, 100, 180, gDoppler, SetDoppler, NULL, NULL);
+CGameMenuItemZBool boolCrosshair("CROSSHAIR:", 3, 66, 110, 180, gAimReticle, SetCrosshair, NULL, NULL);
+CGameMenuItemZBool boolShowWeapons("SHOW WEAPONS:", 3, 66, 120, 180, gShowWeapon, SetShowWeapons, NULL, NULL);
+CGameMenuItemZBool boolSlopeTilting("SLOPE TILTING:", 3, 66, 130, 180, gSlopeTilting, SetSlopeTilting, NULL, NULL);
+CGameMenuItemZBool boolViewBobbing("VIEW BOBBING:", 3, 66, 140, 180, gViewVBobbing, SetViewBobbing, NULL, NULL);
+CGameMenuItemZBool boolViewSwaying("VIEW SWAYING:", 3, 66, 150, 180, gViewHBobbing, SetViewSwaying, NULL, NULL);
+CGameMenuItem7EE34 itemOption2("VIDEO MODE...", 3, 0, 160, 320, 1);
+CGameMenuItemChain itemChainParentalLock("PARENTAL LOCK", 3, 0, 170, 320, 1, &menuParentalLock, -1, NULL, 0);
+
+CGameMenuItemTitle itemControlsTitle("CONTROLS", 1, 160, 20, 2038);
+CGameMenuItemSliderFloat sliderMouseSpeed("Mouse Sensitivity:", 1, 10, 70, 300, CONTROL_MouseSensitivity, 0.5f, 16.f, 0.5f, SetMouseSensitivity, -1,-1);
+CGameMenuItemZBool boolMouseFlipped("Invert Mouse Aim:", 1, 10, 90, 300, gMouseAimingFlipped, SetMouseAimFlipped, NULL, NULL);
+CGameMenuItemSlider sliderTurnSpeed("Key Turn Speed:", 1, 10, 110, 300, gTurnSpeed, 64, 128, 4, SetTurnSpeed, -1, -1);
+CGameMenuItemChain itemChainKeyList("Configure Keys...", 1, 0, 130, 320, 1, &menuKeys, -1, NULL, 0);
+CGameMenuItemChain itemChainKeyReset("Reset Keys (default)...", 1, 0, 150, 320, 1, &menuKeys, -1, ResetKeys, 0);
+CGameMenuItemChain itemChainKeyResetClassic("Reset Keys (classic)...", 1, 0, 170, 320, 1, &menuKeys, -1, ResetKeysClassic, 0);
+
+CGameMenuItemTitle itemMessagesTitle("MESSAGES", 1, 160, 20, 2038);
+CGameMenuItemZBool boolMessages("MESSAGES:", 3, 66, 70, 180, 0, SetMessages, NULL, NULL);
+CGameMenuItemSlider sliderMsgCount("MESSAGE COUNT:", 3, 66, 80, 180, gMessageCount, 1, 16, 1, NULL, -1, -1);
+CGameMenuItemSlider sliderMsgTime("MESSAGE TIME:", 3, 66, 90, 180, gMessageTime, 1, 8, 1, NULL, -1, -1);
+CGameMenuItemZBool boolMsgFont("LARGE FONT:", 3, 66, 100, 180, 0, 0, NULL, NULL);
+CGameMenuItemZBool boolMsgIncoming("INCOMING:", 3, 66, 110, 180, 0, 0, NULL, NULL);
+CGameMenuItemZBool boolMsgSelf("SELF PICKUP:", 3, 66, 120, 180, 0, 0, NULL, NULL);
+CGameMenuItemZBool boolMsgOther("OTHER PICKUP:", 3, 66, 130, 180, 0, 0, NULL, NULL);
+CGameMenuItemZBool boolMsgRespawn("RESPAWN:", 3, 66, 140, 180, 0, 0, NULL, NULL);
+
+CGameMenuItemTitle itemKeysTitle("KEY SETUP", 1, 160, 20, 2038);
+CGameMenuItemKeyList itemKeyList("", 3, 56, 40, 200, 16, NUMGAMEFUNCTIONS, 0);
+
+CGameMenuItemTitle itemSaveTitle("Save Game", 1, 160, 20, 2038);
+CGameMenuItemZEditBitmap itemSaveGame1(NULL, 3, 20, 60, 320, strRestoreGameStrings[0], 16, 1, SaveGame, 0);
+CGameMenuItemZEditBitmap itemSaveGame2(NULL, 3, 20, 70, 320, strRestoreGameStrings[1], 16, 1, SaveGame, 1);
+CGameMenuItemZEditBitmap itemSaveGame3(NULL, 3, 20, 80, 320, strRestoreGameStrings[2], 16, 1, SaveGame, 2);
+CGameMenuItemZEditBitmap itemSaveGame4(NULL, 3, 20, 90, 320, strRestoreGameStrings[3], 16, 1, SaveGame, 3);
+CGameMenuItemZEditBitmap itemSaveGame5(NULL, 3, 20, 100, 320, strRestoreGameStrings[4], 16, 1, SaveGame, 4);
+CGameMenuItemZEditBitmap itemSaveGame6(NULL, 3, 20, 110, 320, strRestoreGameStrings[5], 16, 1, SaveGame, 5);
+CGameMenuItemZEditBitmap itemSaveGame7(NULL, 3, 20, 120, 320, strRestoreGameStrings[6], 16, 1, SaveGame, 6);
+CGameMenuItemZEditBitmap itemSaveGame8(NULL, 3, 20, 130, 320, strRestoreGameStrings[7], 16, 1, SaveGame, 7);
+CGameMenuItemZEditBitmap itemSaveGame9(NULL, 3, 20, 140, 320, strRestoreGameStrings[8], 16, 1, SaveGame, 8);
+CGameMenuItemZEditBitmap itemSaveGame10(NULL, 3, 20, 150, 320, strRestoreGameStrings[9], 16, 1, SaveGame, 9);
+CGameMenuItemBitmapLS itemSaveGamePic(NULL, 3, 0, 0, 2050);
+
+CGameMenuItemTitle itemLoadTitle("Load Game", 1, 160, 20, 2038);
+CGameMenuItemZEditBitmap itemLoadGame1(NULL, 3, 20, 60, 320, strRestoreGameStrings[0], 16, 1, LoadGame, 0);
+CGameMenuItemZEditBitmap itemLoadGame2(NULL, 3, 20, 70, 320, strRestoreGameStrings[1], 16, 1, LoadGame, 1);
+CGameMenuItemZEditBitmap itemLoadGame3(NULL, 3, 20, 80, 320, strRestoreGameStrings[2], 16, 1, LoadGame, 2);
+CGameMenuItemZEditBitmap itemLoadGame4(NULL, 3, 20, 90, 320, strRestoreGameStrings[3], 16, 1, LoadGame, 3);
+CGameMenuItemZEditBitmap itemLoadGame5(NULL, 3, 20, 100, 320, strRestoreGameStrings[4], 16, 1, LoadGame, 4);
+CGameMenuItemZEditBitmap itemLoadGame6(NULL, 3, 20, 110, 320, strRestoreGameStrings[5], 16, 1, LoadGame, 5);
+CGameMenuItemZEditBitmap itemLoadGame7(NULL, 3, 20, 120, 320, strRestoreGameStrings[6], 16, 1, LoadGame, 6);
+CGameMenuItemZEditBitmap itemLoadGame8(NULL, 3, 20, 130, 320, strRestoreGameStrings[7], 16, 1, LoadGame, 7);
+CGameMenuItemZEditBitmap itemLoadGame9(NULL, 3, 20, 140, 320, strRestoreGameStrings[8], 16, 1, LoadGame, 8);
+CGameMenuItemZEditBitmap itemLoadGame10(NULL, 3, 20, 150, 320, strRestoreGameStrings[9], 16, 1, LoadGame, 9);
+CGameMenuItemBitmapLS itemLoadGamePic(NULL, 3, 0, 0, 2518);
+
+CGameMenuItemTitle itemNetStartTitle("MULTIPLAYER", 1, 160, 20, 2038);
+CGameMenuItemZCycle itemNetStart1("GAME", 1, 20, 35, 280, 0, 0, zNetGameTypes, 3, 0);
+CGameMenuItemZCycle itemNetStart2("EPISODE", 1, 20, 50, 280, 0, SetupNetLevels, NULL, 0, 0);
+CGameMenuItemZCycle itemNetStart3("LEVEL", 1, 20, 65, 280, 0, NULL, NULL, 0, 0);
+CGameMenuItemZCycle itemNetStart4("DIFFICULTY", 1, 20, 80, 280, 0, 0, zDiffStrings, 5, 0);
+CGameMenuItemZCycle itemNetStart5("MONSTERS", 1, 20, 95, 280, 0, 0, zMonsterStrings, 3, 0);
+CGameMenuItemZCycle itemNetStart6("WEAPONS", 1, 20, 110, 280, 0, 0, zWeaponStrings, 4, 0);
+CGameMenuItemZCycle itemNetStart7("ITEMS", 1, 20, 125, 280, 0, 0, zItemStrings, 3, 0);
+CGameMenuItemZEdit itemNetStart9("USER MAP:", 1, 20, 155, 280, zUserMapName, 13, 0, NULL, 0);
+CGameMenuItemChain itemNetStart10("START GAME", 1, 20, 170, 280, 0, 0, -1, StartNetGame, 0);
+
+CGameMenuItemText itemLoadingText("LOADING...", 1, 160, 100, 1);
+
+CGameMenuItemTitle itemSoundsTitle("SOUNDS", 1, 160, 20, 2038);
+CGameMenuItemSlider itemSoundsMusic("MUSIC:", 3, 40, 60, 180, MusicVolume, 0, 256, 48, SetMusicVol, -1, -1);
+CGameMenuItemSlider itemSoundsSound("SOUND:", 3, 40, 70, 180, FXVolume, 0, 256, 48, SetSoundVol, -1, -1);
+CGameMenuItemSlider itemSoundsCDAudio("CD AUDIO:", 3, 40, 80, 180, CDVolume, 0, 256, 48, SetCDVol, -1, -1);
+CGameMenuItemZBool itemSounds3DAudio("3D SOUND:", 3, 40, 90, 180, gDoppler, SetDoppler, NULL, NULL);
+
+CGameMenuItemTitle itemQuitTitle("QUIT", 1, 160, 20, 2038);
+CGameMenuItemText itemQuitText1("Do you really want to quit?", 0, 160, 100, 1);
+CGameMenuItemYesNoQuit itemQuitYesNo("[Y/N]", 0, 20, 110, 280, 1, 0);
+
+CGameMenuItemTitle itemRestartTitle("RESTART GAME", 1, 160, 20, 2038);
+CGameMenuItemText itemRestartText1("Do you really want to restart game?", 0, 160, 100, 1);
+CGameMenuItemYesNoQuit itemRestartYesNo("[Y/N]", 0, 20, 110, 280, 1, 1);
+
+CGameMenuItemPicCycle itemCreditsPicCycle(0, 0, NULL, NULL, 0, 0);
+CGameMenuItemPicCycle itemOrderPicCycle(0, 0, NULL, NULL, 0, 0);
+
+CGameMenuItemTitle itemParentalLockTitle("PARENTAL LOCK", 1, 160, 20, 2038);
+CGameMenuItemZBool itemParentalLockToggle("LOCK:", 3, 66, 70, 180, 0, SetParentalLock, NULL, NULL);
+CGameMenuItemPassword itemParentalLockPassword("SET PASSWORD:", 3, 160, 80);
+
+CGameMenuItemPicCycle itemSorryPicCycle(0, 0, NULL, NULL, 0, 0);
+CGameMenuItemText itemSorryText1("Loading and saving games", 0, 160, 90, 1);
+CGameMenuItemText itemSorryText2("not supported", 0, 160, 100, 1);
+CGameMenuItemText itemSorryText3("in this demo version of Blood.", 0, 160, 110, 1);
+
+CGameMenuItemText itemSorry2Text1("Buy the complete version of", 0, 160, 90, 1);
+CGameMenuItemText itemSorry2Text2("Blood for three new episodes", 0, 160, 100, 1);
+CGameMenuItemText itemSorry2Text3("plus eight BloodBath-only levels!", 0, 160, 110, 1);
+
+CGameMenuItemTitle unk_26E06C(" ONLINE ", 1, 160, 20, 2038);
+CGameMenuItem7EA1C unk_26E090("DWANGO", 1, 0, 45, 320, "matt", "DWANGO", 1, -1, NULL, 0);
+CGameMenuItem7EA1C unk_26E0E8("RTIME", 1, 0, 65, 320, "matt", "RTIME", 1, -1, NULL, 0);
+CGameMenuItem7EA1C unk_26E140("HEAT", 1, 0, 85, 320, "matt", "HEAT", 1, -1, NULL, 0);
+CGameMenuItem7EA1C unk_26E198("KALI", 1, 0, 105, 320, "matt", "KALI", 1, -1, NULL, 0);
+CGameMenuItem7EA1C unk_26E1F0("MPATH", 1, 0, 125, 320, "matt", "MPATH", 1, -1, NULL, 0);
+CGameMenuItem7EA1C unk_26E248("TEN", 1, 0, 145, 320, "matt", "TEN", 1, -1, TenProcess, 0);
+
+
+// static int32_t newresolution, newrendermode, newfullscreen, newvsync;
+
+enum resflags_t {
+ RES_FS = 0x1,
+ RES_WIN = 0x2,
+};
+
+#define MAXRESOLUTIONSTRINGLENGTH 19
+
+struct resolution_t {
+ int32_t xdim, ydim;
+ int32_t flags;
+ int32_t bppmax;
+ char name[MAXRESOLUTIONSTRINGLENGTH];
+};
+
+resolution_t gResolution[MAXVALIDMODES];
+int gResolutionNum;
+const char *gResolutionName[MAXVALIDMODES];
+
+CGameMenu menuOptions;
+CGameMenu menuOptionsGame;
+CGameMenu menuOptionsDisplay;
+CGameMenu menuOptionsDisplayColor;
+CGameMenu menuOptionsDisplayMode;
+#ifdef USE_OPENGL
+CGameMenu menuOptionsDisplayPolymost;
+#endif
+CGameMenu menuOptionsSound;
+CGameMenu menuOptionsPlayer;
+CGameMenu menuOptionsControl;
+
+void SetupOptionsSound(CGameMenuItemChain *pItem);
+
+CGameMenuItemTitle itemOptionsTitle("OPTIONS", 1, 160, 20, 2038);
+CGameMenuItemChain itemOptionsChainGame("GAME SETUP", 1, 0, 50, 320, 1, &menuOptionsGame, -1, NULL, 0);
+CGameMenuItemChain itemOptionsChainDisplay("DISPLAY SETUP", 1, 0, 70, 320, 1, &menuOptionsDisplay, -1, NULL, 0);
+CGameMenuItemChain itemOptionsChainSound("SOUND SETUP", 1, 0, 90, 320, 1, &menuOptionsSound, -1, SetupOptionsSound, 0);
+CGameMenuItemChain itemOptionsChainPlayer("PLAYER SETUP", 1, 0, 110, 320, 1, &menuOptionsPlayer, -1, NULL, 0);
+CGameMenuItemChain itemOptionsChainControl("CONTROL SETUP", 1, 0, 130, 320, 1, &menuOptionsControl, -1, NULL, 0);
+CGameMenuItemChain itemOptionsChainOld("OLD MENU", 1, 0, 170, 320, 1, &menuOptionsOld, -1, NULL, 0);
+
+const char *pzAutoAimStrings[] = {
+ "NEVER",
+ "ALWAYS",
+ "HITSCAN ONLY"
+};
+
+const char *pzWeaponSwitchStrings[] = {
+ "NEVER",
+ "IF NEW",
+ "BY RATING"
+};
+
+void SetAutoAim(CGameMenuItemZCycle *);
+void SetLevelStats(CGameMenuItemZBool *);
+void SetPowerupDuration(CGameMenuItemZBool *);
+void SetShowMapTitle(CGameMenuItemZBool*);
+void SetWeaponSwitch(CGameMenuItemZCycle *pItem);
+
+CGameMenuItemTitle itemOptionsGameTitle("GAME SETUP", 1, 160, 20, 2038);
+CGameMenuItemZBool itemOptionsGameBoolShowWeapons("SHOW WEAPONS:", 3, 66, 70, 180, gShowWeapon, SetShowWeapons, NULL, NULL);
+CGameMenuItemZBool itemOptionsGameBoolSlopeTilting("SLOPE TILTING:", 3, 66, 80, 180, gSlopeTilting, SetSlopeTilting, NULL, NULL);
+CGameMenuItemZBool itemOptionsGameBoolViewBobbing("VIEW BOBBING:", 3, 66, 90, 180, gViewVBobbing, SetViewBobbing, NULL, NULL);
+CGameMenuItemZBool itemOptionsGameBoolViewSwaying("VIEW SWAYING:", 3, 66, 100, 180, gViewHBobbing, SetViewSwaying, NULL, NULL);
+CGameMenuItemZCycle itemOptionsGameBoolAutoAim("AUTO AIM:", 3, 66, 110, 180, 0, SetAutoAim, pzAutoAimStrings, ARRAY_SSIZE(pzAutoAimStrings), 0);
+CGameMenuItemZCycle itemOptionsGameWeaponSwitch("EQUIP PICKUPS:", 3, 66, 120, 180, 0, SetWeaponSwitch, pzWeaponSwitchStrings, ARRAY_SSIZE(pzWeaponSwitchStrings), 0);
+CGameMenuItemChain itemOptionsGameChainParentalLock("PARENTAL LOCK", 3, 0, 120, 320, 1, &menuParentalLock, -1, NULL, 0);
+
+CGameMenuItemTitle itemOptionsDisplayTitle("DISPLAY SETUP", 1, 160, 20, 2038);
+CGameMenuItemChain itemOptionsDisplayColor("COLOR CORRECTION", 3, 66, 60, 180, 0, &menuOptionsDisplayColor, -1, NULL, 0);
+CGameMenuItemChain itemOptionsDisplayMode("VIDEO MODE", 3, 66, 70, 180, 0, &menuOptionsDisplayMode, -1, SetupVideoModeMenu, 0);
+CGameMenuItemZBool itemOptionsDisplayBoolCrosshair("CROSSHAIR:", 3, 66, 80, 180, gAimReticle, SetCrosshair, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayBoolCenterHoriz("CENTER HORIZON LINE:", 3, 66, 90, 180, gCenterHoriz, SetCenterHoriz, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayBoolLevelStats("LEVEL STATS:", 3, 66, 100, 180, gLevelStats, SetLevelStats, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayBoolPowerupDuration("POWERUP DURATION:", 3, 66, 110, 180, gPowerupDuration, SetPowerupDuration, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayBoolShowMapTitle("MAP TITLE:", 3, 66, 120, 180, gShowMapTitle, SetShowMapTitle, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayBoolMessages("MESSAGES:", 3, 66, 130, 180, gMessageState, SetMessages, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayBoolWidescreen("WIDESCREEN:", 3, 66, 140, 180, r_usenewaspect, SetWidescreen, NULL, NULL);
+CGameMenuItemSlider itemOptionsDisplayFOV("FOV:", 3, 66, 150, 180, &gFov, 75, 140, 5, SetFOV, -1, -1, kMenuSliderValue);
+#ifdef USE_OPENGL
+CGameMenuItemChain itemOptionsDisplayPolymost("POLYMOST SETUP", 3, 66, 160, 180, 0, &menuOptionsDisplayPolymost, -1, SetupVideoPolymostMenu, 0);
+#endif
+
+const char *pzRendererStrings[] = {
+ "CLASSIC",
+ "POLYMOST"
+};
+
+const int nRendererValues[] = {
+ REND_CLASSIC,
+ REND_POLYMOST
+};
+
+const char *pzVSyncStrings[] = {
+ "ADAPTIVE",
+ "OFF",
+ "ON"
+};
+
+const int nVSyncValues[] = {
+ -1,
+ 0,
+ 1
+};
+
+const char *pzFrameLimitStrings[] = {
+ "30 FPS",
+ "60 FPS",
+ "75 FPS",
+ "100 FPS",
+ "120 FPS",
+ "144 FPS",
+ "165 FPS",
+ "240 FPS"
+};
+
+const int nFrameLimitValues[] = {
+ 30,
+ 60,
+ 75,
+ 100,
+ 120,
+ 144,
+ 165,
+ 240
+};
+
+
+void PreDrawVideoModeMenu(CGameMenuItem *);
+
+CGameMenuItemTitle itemOptionsDisplayModeTitle("VIDEO MODE", 1, 160, 20, 2038);
+CGameMenuItemZCycle itemOptionsDisplayModeResolution("RESOLUTION:", 3, 66, 60, 180, 0, NULL, NULL, 0, 0, true);
+CGameMenuItemZCycle itemOptionsDisplayModeRenderer("RENDERER:", 3, 66, 70, 180, 0, NULL, pzRendererStrings, 2, 0);
+CGameMenuItemZBool itemOptionsDisplayModeFullscreen("FULLSCREEN:", 3, 66, 80, 180, 0, NULL, NULL, NULL);
+CGameMenuItemZCycle itemOptionsDisplayModeVSync("VSYNC:", 3, 66, 90, 180, 0, NULL, pzVSyncStrings, 3, 0);
+CGameMenuItemZCycle itemOptionsDisplayModeFrameLimit("FRAMERATE LIMIT:", 3, 66, 100, 180, 0, UpdateVideoModeMenuFrameLimit, pzFrameLimitStrings, 8, 0);
+CGameMenuItemSlider itemOptionsDisplayModeFPSOffset("FPS OFFSET:", 3, 66, 110, 180, 0, -10, 10, 1, UpdateVideoModeMenuFPSOffset, -1, -1, kMenuSliderValue);
+CGameMenuItemChain itemOptionsDisplayModeApply("APPLY CHANGES", 3, 66, 125, 180, 0, NULL, 0, SetVideoMode, 0);
+
+void PreDrawDisplayColor(CGameMenuItem *);
+
+CGameMenuItemTitle itemOptionsDisplayColorTitle("COLOR CORRECTION", 1, 160, 20, -1);
+CGameMenuItemSliderFloat itemOptionsDisplayColorGamma("GAMMA:", 3, 66, 140, 180, &g_videoGamma, 0.3f, 4.f, 0.1f, UpdateVideoColorMenu, -1, -1, kMenuSliderValue);
+CGameMenuItemSliderFloat itemOptionsDisplayColorContrast("CONTRAST:", 3, 66, 150, 180, &g_videoContrast, 0.1f, 2.7f, 0.05f, UpdateVideoColorMenu, -1, -1, kMenuSliderValue);
+CGameMenuItemSliderFloat itemOptionsDisplayColorBrightness("BRIGHTNESS:", 3, 66, 160, 180, &g_videoBrightness, -0.8f, 0.8f, 0.05f, UpdateVideoColorMenu, -1, -1, kMenuSliderValue);
+CGameMenuItemSliderFloat itemOptionsDisplayColorVisibility("VISIBILITY:", 3, 66, 170, 180, &r_ambientlight, 0.125f, 4.f, 0.125f, UpdateVideoColorMenu, -1, -1, kMenuSliderValue);
+CGameMenuItemChain itemOptionsDisplayColorReset("RESET TO DEFAULTS", 3, 66, 180, 180, 0, NULL, 0, ResetVideoColor, 0);
+
+const char *pzTextureModeStrings[] = {
+ "CLASSIC",
+ "FILTERED"
+};
+
+#ifdef USE_OPENGL
+int nTextureModeValues[] = {
+ TEXFILTER_OFF,
+ TEXFILTER_ON
+};
+#endif
+
+const char *pzAnisotropyStrings[] = {
+ "MAX",
+ "NONE",
+ "2X",
+ "4X",
+ "8X",
+ "16X"
+};
+
+int nAnisotropyValues[] = {
+ 0,
+ 1,
+ 2,
+ 4,
+ 8,
+ 16
+};
+
+const char *pzTexQualityStrings[] = {
+ "FULL",
+ "HALF",
+ "BARF"
+};
+
+
+void UpdateTextureMode(CGameMenuItemZCycle *pItem);
+void UpdateAnisotropy(CGameMenuItemZCycle *pItem);
+void UpdateTrueColorTextures(CGameMenuItemZBool *pItem);
+void UpdateTexQuality(CGameMenuItemZCycle *pItem);
+void UpdatePreloadCache(CGameMenuItemZBool *pItem);
+void UpdateDetailTex(CGameMenuItemZBool *pItem);
+void UpdateGlowTex(CGameMenuItemZBool *pItem);
+void Update3DModels(CGameMenuItemZBool *pItem);
+void UpdateDeliriumBlur(CGameMenuItemZBool *pItem);
+#ifdef USE_OPENGL
+void PreDrawDisplayPolymost(CGameMenuItem *pItem);
+CGameMenuItemTitle itemOptionsDisplayPolymostTitle("POLYMOST SETUP", 1, 160, 20, 2038);
+CGameMenuItemZCycle itemOptionsDisplayPolymostTextureMode("TEXTURE MODE:", 3, 66, 60, 180, 0, UpdateTextureMode, pzTextureModeStrings, 2, 0);
+CGameMenuItemZCycle itemOptionsDisplayPolymostAnisotropy("ANISOTROPY:", 3, 66, 70, 180, 0, UpdateAnisotropy, pzAnisotropyStrings, 6, 0);
+CGameMenuItemZBool itemOptionsDisplayPolymostTrueColorTextures("TRUE COLOR TEXTURES:", 3, 66, 80, 180, 0, UpdateTrueColorTextures, NULL, NULL);
+CGameMenuItemZCycle itemOptionsDisplayPolymostTexQuality("GL TEXTURE QUALITY:", 3, 66, 90, 180, 0, UpdateTexQuality, pzTexQualityStrings, 3, 0);
+CGameMenuItemZBool itemOptionsDisplayPolymostPreloadCache("PRE-LOAD MAP TEXTURES:", 3, 66, 100, 180, 0, UpdatePreloadCache, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayPolymostDetailTex("DETAIL TEXTURES:", 3, 66, 120, 180, 0, UpdateDetailTex, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayPolymostGlowTex("GLOW TEXTURES:", 3, 66, 130, 180, 0, UpdateGlowTex, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayPolymost3DModels("3D MODELS:", 3, 66, 140, 180, 0, Update3DModels, NULL, NULL);
+CGameMenuItemZBool itemOptionsDisplayPolymostDeliriumBlur("DELIRIUM EFFECT BLUR:", 3, 66, 150, 180, 0, UpdateDeliriumBlur, NULL, NULL);
+#endif
+
+void UpdateSoundToggle(CGameMenuItemZBool *pItem);
+void UpdateMusicToggle(CGameMenuItemZBool *pItem);
+void Update3DToggle(CGameMenuItemZBool *pItem);
+void UpdateCDToggle(CGameMenuItemZBool *pItem);
+void UpdateSoundVolume(CGameMenuItemSlider *pItem);
+void UpdateMusicVolume(CGameMenuItemSlider *pItem);
+void UpdateSoundRate(CGameMenuItemZCycle *pItem);
+void UpdateNumVoices(CGameMenuItemSlider *pItem);
+void UpdateMusicDevice(CGameMenuItemZCycle *pItem);
+void SetSound(CGameMenuItemChain *pItem);
+void PreDrawSound(CGameMenuItem *pItem);
+const char *pzSoundRateStrings[] = {
+ "22050HZ",
+ "44100HZ",
+ "48000HZ"
+};
+
+int nSoundRateValues[] = {
+ 22050,
+ 44100,
+ 48000
+};
+
+const char *pzMusicDeviceStrings[] = {
+ "SYSTEM MIDI",
+ "OPL3(SB/ADLIB)"
+};
+
+CGameMenuItemTitle itemOptionsSoundTitle("SOUND SETUP", 1, 160, 20, 2038);
+CGameMenuItemZBool itemOptionsSoundSoundToggle("SOUND:", 3, 66, 60, 180, false, UpdateSoundToggle, NULL, NULL);
+CGameMenuItemZBool itemOptionsSoundMusicToggle("MUSIC:", 3, 66, 70, 180, false, UpdateMusicToggle, NULL, NULL);
+CGameMenuItemZBool itemOptionsSound3DToggle("3D AUDIO:", 3, 66, 80, 180, false, Update3DToggle, NULL, NULL);
+CGameMenuItemSlider itemOptionsSoundSoundVolume("SOUND VOLUME:", 3, 66, 90, 180, &FXVolume, 0, 256, 48, UpdateSoundVolume, -1, -1, kMenuSliderPercent);
+CGameMenuItemSlider itemOptionsSoundMusicVolume("MUSIC VOLUME:", 3, 66, 100, 180, &MusicVolume, 0, 256, 48, UpdateMusicVolume, -1, -1, kMenuSliderPercent);
+CGameMenuItemZCycle itemOptionsSoundSampleRate("SAMPLE RATE:", 3, 66, 110, 180, 0, UpdateSoundRate, pzSoundRateStrings, 3, 0);
+CGameMenuItemSlider itemOptionsSoundNumVoices("VOICES:", 3, 66, 120, 180, NumVoices, 16, 256, 16, UpdateNumVoices, -1, -1, kMenuSliderValue);
+CGameMenuItemZBool itemOptionsSoundCDToggle("REDBOOK AUDIO:", 3, 66, 130, 180, false, UpdateCDToggle, NULL, NULL);
+CGameMenuItemZCycle itemOptionsSoundMusicDevice("MUSIC DEVICE:", 3, 66, 140, 180, 0, UpdateMusicDevice, pzMusicDeviceStrings, 2, 0);
+CGameMenuItemChain itemOptionsSoundApplyChanges("APPLY CHANGES", 3, 66, 150, 180, 0, NULL, 0, SetSound, 0);
+
+
+void UpdatePlayerName(CGameMenuItemZEdit *pItem, CGameMenuEvent *pEvent);
+
+CGameMenuItemTitle itemOptionsPlayerTitle("PLAYER SETUP", 1, 160, 20, 2038);
+CGameMenuItemZEdit itemOptionsPlayerName("PLAYER NAME:", 3, 66, 60, 180, szPlayerName, MAXPLAYERNAME, 0, UpdatePlayerName, 0);
+
+CGameMenu menuOptionsControlKeyboard;
+CGameMenu menuOptionsControlMouse;
+CGameMenu menuOptionsControlMouseButtonAssignment;
+
+void SetupMouseMenu(CGameMenuItemChain *pItem);
+
+CGameMenuItemTitle itemOptionsControlTitle("CONTROL SETUP", 1, 160, 20, 2038);
+CGameMenuItemChain itemOptionsControlKeyboard("KEYBOARD SETUP", 1, 0, 60, 320, 1, &menuOptionsControlKeyboard, -1, NULL, 0);
+CGameMenuItemChain itemOptionsControlMouse("MOUSE SETUP", 1, 0, 80, 320, 1, &menuOptionsControlMouse, -1, SetupMouseMenu, 0);
+
+CGameMenuItemTitle itemOptionsControlKeyboardTitle("KEYBOARD SETUP", 1, 160, 20, 2038);
+CGameMenuItemChain itemOptionsControlKeyboardList("Configure Keys...", 1, 0, 60, 320, 1, &menuKeys, -1, NULL, 0);
+CGameMenuItemChain itemOptionsControlKeyboardReset("Reset Keys (default)...", 1, 0, 80, 320, 1, &menuKeys, -1, ResetKeys, 0);
+CGameMenuItemChain itemOptionsControlKeyboardResetClassic("Reset Keys (classic)...", 1, 0, 100, 320, 1, &menuKeys, -1, ResetKeysClassic, 0);
+
+void SetMouseFilterInput(CGameMenuItemZBool *pItem);
+void SetMouseAimMode(CGameMenuItemZBool *pItem);
+void SetMouseVerticalAim(CGameMenuItemZBool *pItem);
+void SetMouseXScale(CGameMenuItemSlider *pItem);
+void SetMouseYScale(CGameMenuItemSlider *pItem);
+void SetMouseDigitalAxis(CGameMenuItemZCycle *pItem);
+
+void PreDrawControlMouse(CGameMenuItem *pItem);
+
+void SetupMouseButtonMenu(CGameMenuItemChain *pItem);
+
+CGameMenuItemTitle itemOptionsControlMouseTitle("MOUSE SETUP", 1, 160, 20, 2038);
+CGameMenuItemChain itemOptionsControlMouseButton("BUTTON ASSIGNMENT", 3, 66, 60, 180, 0, &menuOptionsControlMouseButtonAssignment, 0, SetupMouseButtonMenu, 0);
+CGameMenuItemSliderFloat itemOptionsControlMouseSensitivity("SENSITIVITY:", 3, 66, 70, 180, &CONTROL_MouseSensitivity, 0.5f, 16.f, 0.5f, SetMouseSensitivity, -1, -1, kMenuSliderValue);
+CGameMenuItemZBool itemOptionsControlMouseAimFlipped("INVERT AIMING:", 3, 66, 80, 180, false, SetMouseAimFlipped, NULL, NULL);
+CGameMenuItemZBool itemOptionsControlMouseFilterInput("FILTER INPUT:", 3, 66, 90, 180, false, SetMouseFilterInput, NULL, NULL);
+CGameMenuItemZBool itemOptionsControlMouseAimMode("AIMING TYPE:", 3, 66, 100, 180, false, SetMouseAimMode, "HOLD", "TOGGLE");
+CGameMenuItemZBool itemOptionsControlMouseVerticalAim("VERTICAL AIMING:", 3, 66, 110, 180, false, SetMouseVerticalAim, NULL, NULL);
+CGameMenuItemSlider itemOptionsControlMouseXScale("X-SCALE:", 3, 66, 120, 180, (int*)&MouseAnalogueScale[0], 0, 65536, 1024, SetMouseXScale, -1, -1, kMenuSliderQ16);
+CGameMenuItemSlider itemOptionsControlMouseYScale("Y-SCALE:", 3, 66, 130, 180, (int*)&MouseAnalogueScale[1], 0, 65536, 1024, SetMouseYScale, -1, -1, kMenuSliderQ16);
+CGameMenuItemZCycle itemOptionsControlMouseDigitalUp("DIGITAL UP", 3, 66, 140, 180, 0, SetMouseDigitalAxis, NULL, 0, 0, true);
+CGameMenuItemZCycle itemOptionsControlMouseDigitalDown("DIGITAL DOWN", 3, 66, 150, 180, 0, SetMouseDigitalAxis, NULL, 0, 0, true);
+CGameMenuItemZCycle itemOptionsControlMouseDigitalLeft("DIGITAL LEFT", 3, 66, 160, 180, 0, SetMouseDigitalAxis, NULL, 0, 0, true);
+CGameMenuItemZCycle itemOptionsControlMouseDigitalRight("DIGITAL RIGHT", 3, 66, 170, 180, 0, SetMouseDigitalAxis, NULL, 0, 0, true);
+
+void SetupNetworkMenu(void);
+void SetupNetworkHostMenu(CGameMenuItemChain *pItem);
+void SetupNetworkJoinMenu(CGameMenuItemChain *pItem);
+void NetworkHostGame(CGameMenuItemChain *pItem);
+void NetworkJoinGame(CGameMenuItemChain *pItem);
+
+char zNetAddressBuffer[16] = "localhost";
+char zNetPortBuffer[6];
+
+CGameMenuItemTitle itemNetworkTitle("MULTIPLAYER", 1, 160, 20, 2038);
+CGameMenuItemChain itemNetworkHost("HOST A GAME", 1, 0, 80, 320, 1, &menuNetworkHost, -1, SetupNetworkHostMenu, 0);
+CGameMenuItemChain itemNetworkJoin("JOIN A GAME", 1, 0, 100, 320, 1, &menuNetworkJoin, -1, SetupNetworkJoinMenu, 0);
+
+CGameMenuItemTitle itemNetworkHostTitle("HOST A GAME", 1, 160, 20, 2038);
+CGameMenuItemSlider itemNetworkHostPlayerNum("PLAYER NUMBER:", 3, 66, 70, 180, 1, 2, kMaxPlayers, 1, NULL, -1, -1, kMenuSliderValue);
+CGameMenuItemZEdit itemNetworkHostPort("NETWORK PORT:", 3, 66, 80, 180, zNetPortBuffer, 6, 0, NULL, 0);
+CGameMenuItemChain itemNetworkHostHost("HOST A GAME", 3, 66, 100, 180, 1, NULL, -1, NetworkHostGame, 0);
+
+CGameMenuItemTitle itemNetworkJoinTitle("JOIN A GAME", 1, 160, 20, 2038);
+CGameMenuItemZEdit itemNetworkJoinAddress("NETWORK ADDRESS:", 3, 66, 70, 180, zNetAddressBuffer, 16, 0, NULL, 0);
+CGameMenuItemZEdit itemNetworkJoinPort("NETWORK PORT:", 3, 66, 80, 180, zNetPortBuffer, 6, 0, NULL, 0);
+CGameMenuItemChain itemNetworkJoinJoin("JOIN A GAME", 3, 66, 100, 180, 1, NULL, -1, NetworkJoinGame, 0);
+
+// There is no better way to do this than manually.
+
+#define MENUMOUSEFUNCTIONS 12
+
+static char const *MenuMouseNames[MENUMOUSEFUNCTIONS] = {
+ "Button 1",
+ "Double Button 1",
+ "Button 2",
+ "Double Button 2",
+ "Button 3",
+ "Double Button 3",
+
+ "Wheel Up",
+ "Wheel Down",
+
+ "Button 4",
+ "Double Button 4",
+ "Button 5",
+ "Double Button 5",
+};
+
+static int32_t MenuMouseDataIndex[MENUMOUSEFUNCTIONS][2] = {
+ { 0, 0, },
+ { 0, 1, },
+ { 1, 0, },
+ { 1, 1, },
+ { 2, 0, },
+ { 2, 1, },
+
+ // note the mouse wheel
+ { 4, 0, },
+ { 5, 0, },
+
+ { 3, 0, },
+ { 3, 1, },
+ { 6, 0, },
+ { 6, 1, },
+};
+
+void SetMouseButton(CGameMenuItemZCycle *pItem);
+
+CGameMenuItemZCycle *pItemOptionsControlMouseButton[MENUMOUSEFUNCTIONS];
+
+void SetupLoadingScreen(void)
+{
+ menuLoading.Add(&itemLoadingText, true);
+}
+
+void SetupKeyListMenu(void)
+{
+ menuKeys.Add(&itemKeysTitle, false);
+ menuKeys.Add(&itemKeyList, true);
+ menuKeys.Add(&itemBloodQAV, false);
+}
+
+void SetupMessagesMenu(void)
+{
+ menuMessages.Add(&itemMessagesTitle, false);
+ menuMessages.Add(&boolMessages, true);
+ menuMessages.Add(&sliderMsgCount, false);
+ menuMessages.Add(&sliderMsgTime, false);
+ menuMessages.Add(&boolMsgFont, false);
+ menuMessages.Add(&boolMsgIncoming, false);
+ menuMessages.Add(&boolMsgSelf, false);
+ menuMessages.Add(&boolMsgOther, false);
+ menuMessages.Add(&boolMsgRespawn, false);
+ menuMessages.Add(&itemBloodQAV, false);
+}
+
+void SetupControlsMenu(void)
+{
+ sliderMouseSpeed.fValue = ClipRangeF(CONTROL_MouseSensitivity, sliderMouseSpeed.fRangeLow, sliderMouseSpeed.fRangeHigh);
+ sliderTurnSpeed.nValue = ClipRange(gTurnSpeed, sliderTurnSpeed.nRangeLow, sliderTurnSpeed.nRangeHigh);
+ boolMouseFlipped.at20 = gMouseAimingFlipped;
+ menuControls.Add(&itemControlsTitle, false);
+ menuControls.Add(&sliderMouseSpeed, true);
+ menuControls.Add(&boolMouseFlipped, false);
+ menuControls.Add(&sliderTurnSpeed, false);
+ menuControls.Add(&itemChainKeyList, false);
+ menuControls.Add(&itemChainKeyReset, false);
+ menuControls.Add(&itemChainKeyResetClassic, false);
+ menuControls.Add(&itemBloodQAV, false);
+}
+
+void SetupOptionsOldMenu(void)
+{
+ sliderDetail.nValue = ClipRange(gDetail, sliderDetail.nRangeLow, sliderDetail.nRangeHigh);
+ sliderGamma.nValue = ClipRange(gGamma, sliderGamma.nRangeLow, sliderGamma.nRangeHigh);
+ sliderMusic.nValue = ClipRange(MusicVolume, sliderMusic.nRangeLow, sliderMusic.nRangeHigh);
+ sliderSound.nValue = ClipRange(FXVolume, sliderSound.nRangeLow, sliderSound.nRangeHigh);
+ bool3DAudio.at20 = gDoppler;
+ boolCrosshair.at20 = gAimReticle;
+ boolShowWeapons.at20 = gShowWeapon;
+ boolSlopeTilting.at20 = gSlopeTilting;
+ boolViewBobbing.at20 = gViewVBobbing;
+ boolViewSwaying.at20 = gViewHBobbing;
+ boolMessages.at20 = gGameMessageMgr.at0;
+ menuOptionsOld.Add(&itemOptionsTitle, false);
+ menuOptionsOld.Add(&itemOption1, true);
+ menuOptionsOld.Add(&sliderDetail, false);
+ menuOptionsOld.Add(&sliderGamma, false);
+ menuOptionsOld.Add(&sliderMusic, false);
+ menuOptionsOld.Add(&sliderSound, false);
+ menuOptionsOld.Add(&sliderCDAudio, false);
+ menuOptionsOld.Add(&bool3DAudio, false);
+ menuOptionsOld.Add(&boolCrosshair, false);
+ menuOptionsOld.Add(&boolShowWeapons, false);
+ menuOptionsOld.Add(&boolSlopeTilting, false);
+ menuOptionsOld.Add(&boolViewBobbing, false);
+ menuOptionsOld.Add(&boolViewSwaying, false);
+ menuOptionsOld.Add(&itemOption2, false);
+ menuOptionsOld.Add(&itemChainParentalLock, false);
+ menuOptionsOld.Add(&itemBloodQAV, false);
+}
+
+void SetupDifficultyMenu(void)
+{
+ menuDifficulty.Add(&itemDifficultyTitle, false);
+ menuDifficulty.Add(&itemDifficulty1, false);
+ menuDifficulty.Add(&itemDifficulty2, false);
+ menuDifficulty.Add(&itemDifficulty3, true);
+ menuDifficulty.Add(&itemDifficulty4, false);
+ menuDifficulty.Add(&itemDifficulty5, false);
+ menuDifficulty.Add(&itemBloodQAV, false);
+}
+
+void SetupEpisodeMenu(void)
+{
+ menuEpisode.Add(&itemEpisodesTitle, false);
+ bool unk = false;
+ int height;
+ gMenuTextMgr.GetFontInfo(1, NULL, NULL, &height);
+ int j = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ EPISODEINFO *pEpisode = &gEpisodeInfo[i];
+ if (!pEpisode->bloodbath || gGameOptions.nGameType != 0)
+ {
+ if (j < gEpisodeCount)
+ {
+ CGameMenuItemChain7F2F0 *pEpisodeItem = &itemEpisodes[j];
+ pEpisodeItem->m_nFont = 1;
+ pEpisodeItem->m_nX = 0;
+ pEpisodeItem->m_nWidth = 320;
+ pEpisodeItem->at20 = 1;
+ pEpisodeItem->m_pzText = pEpisode->at0;
+ pEpisodeItem->m_nY = 55+(height+8)*j;
+ pEpisodeItem->at34 = i;
+ if (!unk || j == 0)
+ {
+ pEpisodeItem = &itemEpisodes[j];
+ pEpisodeItem->at24 = &menuDifficulty;
+ pEpisodeItem->at28 = 3;
+ }
+ else
+ {
+ pEpisodeItem->at24 = &menuSorry2;
+ pEpisodeItem->at28 = 1;
+ }
+ pEpisodeItem = &itemEpisodes[j];
+ pEpisodeItem->bCanSelect = 1;
+ pEpisodeItem->bEnable = 1;
+ bool first = j == 0;
+ menuEpisode.Add(&itemEpisodes[j], first);
+ if (first)
+ SetupLevelMenuItem(j);
+ }
+ j++;
+ }
+ }
+ menuEpisode.Add(&itemBloodQAV, false);
+}
+
+void SetupMainMenu(void)
+{
+ menuMain.Add(&itemMainTitle, false);
+ menuMain.Add(&itemMain1, true);
+ if (gGameOptions.nGameType > 0)
+ {
+ itemMain1.at24 = &menuNetStart;
+ itemMain1.at28 = 2;
+ }
+ else
+ {
+ itemMain1.at24 = &menuEpisode;
+ itemMain1.at28 = -1;
+ }
+ menuMain.Add(&itemMain2, false);
+ menuMain.Add(&itemMain3, false);
+ menuMain.Add(&itemMain4, false);
+ menuMain.Add(&itemMain5, false);
+ menuMain.Add(&itemMain6, false);
+ menuMain.Add(&itemMain7, false);
+ menuMain.Add(&itemBloodQAV, false);
+}
+
+void SetupMainMenuWithSave(void)
+{
+ menuMainWithSave.Add(&itemMainSaveTitle, false);
+ menuMainWithSave.Add(&itemMainSave1, true);
+ if (gGameOptions.nGameType > 0)
+ {
+ itemMainSave1.at24 = &menuNetStart;
+ itemMainSave1.at28 = 2;
+ }
+ else
+ {
+ itemMainSave1.at24 = &menuEpisode;
+ itemMainSave1.at28 = -1;
+ }
+ menuMainWithSave.Add(&itemMainSave2, false);
+ menuMainWithSave.Add(&itemMainSave3, false);
+ menuMainWithSave.Add(&itemMainSave4, false);
+ menuMainWithSave.Add(&itemMainSave5, false);
+ menuMainWithSave.Add(&itemMainSave6, false);
+ menuMainWithSave.Add(&itemMainSave7, false);
+ menuMainWithSave.Add(&itemMainSave8, false);
+ menuMainWithSave.Add(&itemBloodQAV, false);
+}
+
+void SetupNetStartMenu(void)
+{
+ bool oneEpisode = false;
+ menuNetStart.Add(&itemNetStartTitle, false);
+ menuNetStart.Add(&itemNetStart1, false);
+ for (int i = 0; i < (oneEpisode ? 1 : 6); i++)
+ {
+ EPISODEINFO *pEpisode = &gEpisodeInfo[i];
+ if (i < gEpisodeCount)
+ itemNetStart2.Add(pEpisode->at0, i == 0);
+ }
+ menuNetStart.Add(&itemNetStart2, false);
+ menuNetStart.Add(&itemNetStart3, false);
+ menuNetStart.Add(&itemNetStart4, false);
+ menuNetStart.Add(&itemNetStart5, false);
+ menuNetStart.Add(&itemNetStart6, false);
+ menuNetStart.Add(&itemNetStart7, false);
+ menuNetStart.Add(&itemNetStart9, false);
+ menuNetStart.Add(&itemNetStart10, false);
+ itemNetStart1.SetTextIndex(1);
+ itemNetStart4.SetTextIndex(2);
+ itemNetStart5.SetTextIndex(0);
+ itemNetStart6.SetTextIndex(1);
+ itemNetStart7.SetTextIndex(1);
+ menuNetStart.Add(&itemBloodQAV, false);
+}
+
+void SetupSaveGameMenu(void)
+{
+ menuSaveGame.Add(&itemSaveTitle, false);
+ menuSaveGame.Add(&itemSaveGame1, true);
+ menuSaveGame.Add(&itemSaveGame2, false);
+ menuSaveGame.Add(&itemSaveGame3, false);
+ menuSaveGame.Add(&itemSaveGame4, false);
+ menuSaveGame.Add(&itemSaveGame5, false);
+ menuSaveGame.Add(&itemSaveGame6, false);
+ menuSaveGame.Add(&itemSaveGame7, false);
+ menuSaveGame.Add(&itemSaveGame8, false);
+ menuSaveGame.Add(&itemSaveGame9, false);
+ menuSaveGame.Add(&itemSaveGame10, false);
+ menuSaveGame.Add(&itemSaveGamePic, false);
+ menuSaveGame.Add(&itemBloodQAV, false);
+
+ itemSaveGame1.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[0], ""))
+ itemSaveGame1.at37 = 1;
+
+ itemSaveGame2.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[1], ""))
+ itemSaveGame2.at37 = 1;
+
+ itemSaveGame3.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[2], ""))
+ itemSaveGame3.at37 = 1;
+
+ itemSaveGame4.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[3], ""))
+ itemSaveGame4.at37 = 1;
+
+ itemSaveGame5.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[4], ""))
+ itemSaveGame5.at37 = 1;
+
+ itemSaveGame6.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[5], ""))
+ itemSaveGame6.at37 = 1;
+
+ itemSaveGame7.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[6], ""))
+ itemSaveGame7.at37 = 1;
+
+ itemSaveGame8.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[7], ""))
+ itemSaveGame8.at37 = 1;
+
+ itemSaveGame9.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[8], ""))
+ itemSaveGame9.at37 = 1;
+
+ itemSaveGame10.at2c = &itemSaveGamePic;
+ if (!strcmp(strRestoreGameStrings[9], ""))
+ itemSaveGame10.at37 = 1;
+}
+
+void SetupLoadGameMenu(void)
+{
+ menuLoadGame.Add(&itemLoadTitle, false);
+ menuLoadGame.Add(&itemLoadGame1, true);
+ menuLoadGame.Add(&itemLoadGame2, false);
+ menuLoadGame.Add(&itemLoadGame3, false);
+ menuLoadGame.Add(&itemLoadGame4, false);
+ menuLoadGame.Add(&itemLoadGame5, false);
+ menuLoadGame.Add(&itemLoadGame6, false);
+ menuLoadGame.Add(&itemLoadGame7, false);
+ menuLoadGame.Add(&itemLoadGame8, false);
+ menuLoadGame.Add(&itemLoadGame9, false);
+ menuLoadGame.Add(&itemLoadGame10, false);
+ menuLoadGame.Add(&itemLoadGamePic, false);
+ itemLoadGame1.at35 = 0;
+ itemLoadGame2.at35 = 0;
+ itemLoadGame3.at35 = 0;
+ itemLoadGame4.at35 = 0;
+ itemLoadGame5.at35 = 0;
+ itemLoadGame6.at35 = 0;
+ itemLoadGame7.at35 = 0;
+ itemLoadGame8.at35 = 0;
+ itemLoadGame9.at35 = 0;
+ itemLoadGame10.at35 = 0;
+ itemLoadGame1.at2c = &itemLoadGamePic;
+ itemLoadGame2.at2c = &itemLoadGamePic;
+ itemLoadGame3.at2c = &itemLoadGamePic;
+ itemLoadGame4.at2c = &itemLoadGamePic;
+ itemLoadGame5.at2c = &itemLoadGamePic;
+ itemLoadGame6.at2c = &itemLoadGamePic;
+ itemLoadGame7.at2c = &itemLoadGamePic;
+ itemLoadGame8.at2c = &itemLoadGamePic;
+ itemLoadGame9.at2c = &itemLoadGamePic;
+ itemLoadGame10.at2c = &itemLoadGamePic;
+ menuLoadGame.Add(&itemBloodQAV, false);
+}
+
+void SetupSoundsMenu(void)
+{
+ itemSoundsMusic.nValue = ClipRange(MusicVolume, itemSoundsMusic.nRangeLow, itemSoundsMusic.nRangeHigh);
+ itemSoundsSound.nValue = ClipRange(FXVolume, itemSoundsSound.nRangeLow, itemSoundsSound.nRangeHigh);
+ menuSounds.Add(&itemSoundsTitle, false);
+ menuSounds.Add(&itemSoundsMusic, true);
+ menuSounds.Add(&itemSoundsSound, false);
+ menuSounds.Add(&itemSoundsCDAudio, false);
+ menuSounds.Add(&itemSounds3DAudio, false);
+ menuSounds.Add(&itemBloodQAV, false);
+}
+
+void SetupQuitMenu(void)
+{
+ menuQuit.Add(&itemQuitTitle, false);
+ menuQuit.Add(&itemQuitText1, false);
+ menuQuit.Add(&itemQuitYesNo, true);
+ menuQuit.Add(&itemBloodQAV, false);
+
+ menuRestart.Add(&itemRestartTitle, false);
+ menuRestart.Add(&itemRestartText1, false);
+ menuRestart.Add(&itemRestartYesNo, true);
+ menuRestart.Add(&itemBloodQAV, false);
+}
+
+void SetupHelpOrderMenu(void)
+{
+ menuOrder.Add(&itemHelp4QAV, true);
+ menuOrder.Add(&itemHelp5QAV, false);
+ menuOrder.Add(&itemHelp3QAV, false);
+ menuOrder.Add(&itemHelp3BQAV, false);
+ itemHelp4QAV.bEnable = 1;
+ itemHelp4QAV.bNoDraw = 1;
+ itemHelp5QAV.bEnable = 1;
+ itemHelp5QAV.bNoDraw = 1;
+ itemHelp3QAV.bEnable = 1;
+ itemHelp3QAV.bNoDraw = 1;
+ itemHelp3BQAV.bEnable = 1;
+ itemHelp3BQAV.bNoDraw = 1;
+}
+
+void SetupCreditsMenu(void)
+{
+ menuCredits.Add(&itemCreditsQAV, true);
+ itemCreditsQAV.bEnable = 1;
+ itemCreditsQAV.bNoDraw = 1;
+}
+
+void SetupParentalLockMenu(void)
+{
+ itemParentalLockToggle.at20 = gbAdultContent;
+ strcpy(itemParentalLockPassword.at20, gzAdultPassword);
+ menuParentalLock.Add(&itemParentalLockTitle, false);
+ menuParentalLock.Add(&itemParentalLockToggle, true);
+ menuParentalLock.Add(&itemParentalLockPassword, false);
+ menuParentalLock.Add(&itemBloodQAV, false);
+}
+
+void SetupSorry3Menu(void)
+{
+ menuPlayOnline.Add(&unk_26E06C, false);
+ menuPlayOnline.Add(&unk_26E090, true);
+ menuPlayOnline.Add(&unk_26E0E8, false);
+ menuPlayOnline.Add(&unk_26E140, false);
+ menuPlayOnline.Add(&unk_26E198, false);
+ menuPlayOnline.Add(&unk_26E1F0, false);
+ menuPlayOnline.Add(&unk_26E248, false);
+ menuPlayOnline.Add(&itemBloodQAV, false);
+}
+
+void SetupSorryMenu(void)
+{
+ menuSorry.Add(&itemSorryPicCycle, true);
+ menuSorry.Add(&itemSorryText1, false);
+ menuSorry.Add(&itemSorryText3, false);
+ menuSorry.Add(&itemBloodQAV, false);
+}
+
+void SetupSorry2Menu(void)
+{
+ menuSorry2.Add(&itemSorryPicCycle, true);
+ menuSorry2.Add(&itemSorry2Text1, false);
+ menuSorry2.Add(&itemSorry2Text2, false);
+ menuSorry2.Add(&itemSorry2Text3, false);
+ menuSorry2.Add(&itemBloodQAV, false);
+}
+
+void SetupOptionsMenu(void)
+{
+ menuOptions.Add(&itemOptionsTitle, false);
+ menuOptions.Add(&itemOptionsChainGame, true);
+ menuOptions.Add(&itemOptionsChainDisplay, false);
+ menuOptions.Add(&itemOptionsChainSound, false);
+ menuOptions.Add(&itemOptionsChainPlayer, false);
+ menuOptions.Add(&itemOptionsChainControl, false);
+ //menuOptions.Add(&itemOptionsChainOld, false);
+ menuOptions.Add(&itemBloodQAV, false);
+
+ menuOptionsGame.Add(&itemOptionsGameTitle, false);
+ menuOptionsGame.Add(&itemOptionsGameBoolShowWeapons, true);
+ menuOptionsGame.Add(&itemOptionsGameBoolSlopeTilting, false);
+ menuOptionsGame.Add(&itemOptionsGameBoolViewBobbing, false);
+ menuOptionsGame.Add(&itemOptionsGameBoolViewSwaying, false);
+ menuOptionsGame.Add(&itemOptionsGameBoolAutoAim, false);
+ menuOptionsGame.Add(&itemOptionsGameWeaponSwitch, false);
+ //menuOptionsGame.Add(&itemOptionsGameChainParentalLock, false);
+ menuOptionsGame.Add(&itemBloodQAV, false);
+ itemOptionsGameBoolShowWeapons.at20 = gShowWeapon;
+ itemOptionsGameBoolSlopeTilting.at20 = gSlopeTilting;
+ itemOptionsGameBoolViewBobbing.at20 = gViewVBobbing;
+ itemOptionsGameBoolViewSwaying.at20 = gViewHBobbing;
+ itemOptionsGameBoolAutoAim.m_nFocus = gAutoAim;
+ itemOptionsGameWeaponSwitch.m_nFocus = (gWeaponSwitch&1) ? ((gWeaponSwitch&2) ? 1 : 2) : 0;
+
+ menuOptionsDisplay.Add(&itemOptionsDisplayTitle, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayColor, true);
+ menuOptionsDisplay.Add(&itemOptionsDisplayMode, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolCrosshair, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolCenterHoriz, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolLevelStats, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolPowerupDuration, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolShowMapTitle, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolMessages, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayBoolWidescreen, false);
+ menuOptionsDisplay.Add(&itemOptionsDisplayFOV, false);
+#ifdef USE_OPENGL
+ menuOptionsDisplay.Add(&itemOptionsDisplayPolymost, false);
+#endif
+ menuOptionsDisplay.Add(&itemBloodQAV, false);
+ itemOptionsDisplayBoolCrosshair.at20 = gAimReticle;
+ itemOptionsDisplayBoolCenterHoriz.at20 = gCenterHoriz;
+ itemOptionsDisplayBoolLevelStats.at20 = gLevelStats;
+ itemOptionsDisplayBoolPowerupDuration.at20 = gPowerupDuration;
+ itemOptionsDisplayBoolShowMapTitle.at20 = gShowMapTitle;
+ itemOptionsDisplayBoolMessages.at20 = gMessageState;
+ itemOptionsDisplayBoolWidescreen.at20 = r_usenewaspect;
+
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeTitle, false);
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeResolution, true);
+ // prepare video setup
+ for (int i = 0; i < validmodecnt; ++i)
+ {
+ int j;
+ for (j = 0; j < gResolutionNum; ++j)
+ {
+ if (validmode[i].xdim == gResolution[j].xdim && validmode[i].ydim == gResolution[j].ydim)
+ {
+ gResolution[j].flags |= validmode[i].fs ? RES_FS : RES_WIN;
+ Bsnprintf(gResolution[j].name, MAXRESOLUTIONSTRINGLENGTH, "%d x %d%s", gResolution[j].xdim, gResolution[j].ydim, (gResolution[j].flags & RES_FS) ? "" : "Win");
+ gResolutionName[j] = gResolution[j].name;
+ if (validmode[i].bpp > gResolution[j].bppmax)
+ gResolution[j].bppmax = validmode[i].bpp;
+ break;
+ }
+ }
+
+ if (j == gResolutionNum) // no match found
+ {
+ gResolution[j].xdim = validmode[i].xdim;
+ gResolution[j].ydim = validmode[i].ydim;
+ gResolution[j].bppmax = validmode[i].bpp;
+ gResolution[j].flags = validmode[i].fs ? RES_FS : RES_WIN;
+ Bsnprintf(gResolution[j].name, MAXRESOLUTIONSTRINGLENGTH, "%d x %d%s", gResolution[j].xdim, gResolution[j].ydim, (gResolution[j].flags & RES_FS) ? "" : "Win");
+ gResolutionName[j] = gResolution[j].name;
+ ++gResolutionNum;
+ }
+ }
+ itemOptionsDisplayModeResolution.SetTextArray(gResolutionName, gResolutionNum, 0);
+#ifdef USE_OPENGL
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeRenderer, false);
+#endif
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeFullscreen, false);
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeVSync, false);
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeFrameLimit, false);
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeFPSOffset, false);
+ menuOptionsDisplayMode.Add(&itemOptionsDisplayModeApply, false);
+ menuOptionsDisplayMode.Add(&itemBloodQAV, false);
+
+#ifdef USE_OPENGL
+ itemOptionsDisplayModeRenderer.pPreDrawCallback = PreDrawVideoModeMenu;
+#endif
+ itemOptionsDisplayModeFullscreen.pPreDrawCallback = PreDrawVideoModeMenu;
+ itemOptionsDisplayModeFPSOffset.pPreDrawCallback = PreDrawVideoModeMenu;
+
+ menuOptionsDisplayColor.Add(&itemOptionsDisplayColorTitle, false);
+ menuOptionsDisplayColor.Add(&itemOptionsDisplayColorGamma, true);
+ menuOptionsDisplayColor.Add(&itemOptionsDisplayColorContrast, false);
+ menuOptionsDisplayColor.Add(&itemOptionsDisplayColorBrightness, false);
+ menuOptionsDisplayColor.Add(&itemOptionsDisplayColorVisibility, false);
+ menuOptionsDisplayColor.Add(&itemOptionsDisplayColorReset, false);
+ menuOptionsDisplayColor.Add(&itemBloodQAV, false);
+
+ itemOptionsDisplayColorContrast.pPreDrawCallback = PreDrawDisplayColor;
+ itemOptionsDisplayColorBrightness.pPreDrawCallback = PreDrawDisplayColor;
+
+#ifdef USE_OPENGL
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostTitle, false);
+ //menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostTextureMode, true);
+ //menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostAnisotropy, false);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostTrueColorTextures, true);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostTexQuality, false);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostPreloadCache, false);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostDetailTex, false);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostGlowTex, false);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymost3DModels, false);
+ menuOptionsDisplayPolymost.Add(&itemOptionsDisplayPolymostDeliriumBlur, false);
+ menuOptionsDisplayPolymost.Add(&itemBloodQAV, false);
+
+ itemOptionsDisplayPolymostTexQuality.pPreDrawCallback = PreDrawDisplayPolymost;
+ itemOptionsDisplayPolymostPreloadCache.pPreDrawCallback = PreDrawDisplayPolymost;
+ itemOptionsDisplayPolymostDetailTex.pPreDrawCallback = PreDrawDisplayPolymost;
+ itemOptionsDisplayPolymostGlowTex.pPreDrawCallback = PreDrawDisplayPolymost;
+#endif
+
+ menuOptionsSound.Add(&itemOptionsSoundTitle, false);
+ menuOptionsSound.Add(&itemOptionsSoundSoundToggle, true);
+ menuOptionsSound.Add(&itemOptionsSoundMusicToggle, false);
+ menuOptionsSound.Add(&itemOptionsSound3DToggle, false);
+ menuOptionsSound.Add(&itemOptionsSoundSoundVolume, false);
+ menuOptionsSound.Add(&itemOptionsSoundMusicVolume, false);
+ menuOptionsSound.Add(&itemOptionsSoundSampleRate, false);
+ menuOptionsSound.Add(&itemOptionsSoundNumVoices, false);
+ menuOptionsSound.Add(&itemOptionsSoundCDToggle, false);
+ menuOptionsSound.Add(&itemOptionsSoundMusicDevice, false);
+ menuOptionsSound.Add(&itemOptionsSoundApplyChanges, false);
+ menuOptionsSound.Add(&itemBloodQAV, false);
+
+ menuOptionsPlayer.Add(&itemOptionsPlayerTitle, false);
+ menuOptionsPlayer.Add(&itemOptionsPlayerName, true);
+ menuOptionsPlayer.Add(&itemBloodQAV, false);
+
+ menuOptionsControl.Add(&itemOptionsControlTitle, false);
+ menuOptionsControl.Add(&itemOptionsControlKeyboard, true);
+ menuOptionsControl.Add(&itemOptionsControlMouse, false);
+ menuOptionsControl.Add(&itemBloodQAV, false);
+
+ menuOptionsControlKeyboard.Add(&itemOptionsControlKeyboardTitle, false);
+ menuOptionsControlKeyboard.Add(&itemOptionsControlKeyboardList, true);
+ menuOptionsControlKeyboard.Add(&itemOptionsControlKeyboardReset, false);
+ menuOptionsControlKeyboard.Add(&itemOptionsControlKeyboardResetClassic, false);
+ menuOptionsControlKeyboard.Add(&itemBloodQAV, false);
+
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseTitle, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseButton, true);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseSensitivity, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseAimFlipped, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseFilterInput, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseAimMode, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseVerticalAim, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseXScale, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseYScale, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseDigitalUp, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseDigitalDown, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseDigitalLeft, false);
+ menuOptionsControlMouse.Add(&itemOptionsControlMouseDigitalRight, false);
+ menuOptionsControlMouse.Add(&itemBloodQAV, false);
+
+ itemOptionsControlMouseDigitalUp.SetTextArray(pzGamefuncsStrings, NUMGAMEFUNCTIONS+1, 0);
+ itemOptionsControlMouseDigitalDown.SetTextArray(pzGamefuncsStrings, NUMGAMEFUNCTIONS+1, 0);
+ itemOptionsControlMouseDigitalLeft.SetTextArray(pzGamefuncsStrings, NUMGAMEFUNCTIONS+1, 0);
+ itemOptionsControlMouseDigitalRight.SetTextArray(pzGamefuncsStrings, NUMGAMEFUNCTIONS+1, 0);
+
+ itemOptionsControlMouseVerticalAim.pPreDrawCallback = PreDrawControlMouse;
+
+ menuOptionsControlMouseButtonAssignment.Add(&itemOptionsControlMouseTitle, false);
+ int y = 60;
+ for (int i = 0; i < MENUMOUSEFUNCTIONS; i++)
+ {
+ pItemOptionsControlMouseButton[i] = new CGameMenuItemZCycle(MenuMouseNames[i], 3, 66, y, 180, 0, SetMouseButton, pzGamefuncsStrings, NUMGAMEFUNCTIONS+1, 0, true);
+ dassert(pItemOptionsControlMouseButton[i] != NULL);
+ menuOptionsControlMouseButtonAssignment.Add(pItemOptionsControlMouseButton[i], i == 0);
+ y += 10;
+ }
+ menuOptionsControlMouseButtonAssignment.Add(&itemBloodQAV, false);
+}
+
+void SetupMenus(void)
+{
+ // prepare gamefuncs and keys
+ pzGamefuncsStrings[0] = MenuGameFuncNone;
+ nGamefuncsValues[0] = -1;
+ int k = 1;
+ for (int i = 0; i < NUMGAMEFUNCTIONS; ++i)
+ {
+ Bstrcpy(MenuGameFuncs[i], gamefunctions[i]);
+
+ for (int j = 0; j < MAXGAMEFUNCLEN; ++j)
+ if (MenuGameFuncs[i][j] == '_')
+ MenuGameFuncs[i][j] = ' ';
+
+ if (gamefunctions[i][0] != '\0')
+ {
+ pzGamefuncsStrings[k] = MenuGameFuncs[i];
+ nGamefuncsValues[k] = i;
+ ++k;
+ }
+ }
+
+ nGamefuncsNum = k;
+
+ SetupLoadingScreen();
+ SetupKeyListMenu();
+ SetupMessagesMenu();
+ SetupControlsMenu();
+ SetupSaveGameMenu();
+ SetupLoadGameMenu();
+ SetupOptionsOldMenu();
+ SetupCreditsMenu();
+ SetupHelpOrderMenu();
+ SetupSoundsMenu();
+ SetupDifficultyMenu();
+ SetupEpisodeMenu();
+ SetupMainMenu();
+ SetupMainMenuWithSave();
+ SetupNetStartMenu();
+ SetupQuitMenu();
+ SetupParentalLockMenu();
+ SetupSorryMenu();
+ SetupSorry2Menu();
+ SetupSorry3Menu();
+
+ SetupOptionsMenu();
+ SetupNetworkMenu();
+}
+
+void UpdateNetworkMenus(void)
+{
+ if (gGameOptions.nGameType > 0)
+ {
+ itemMain1.at24 = &menuNetStart;
+ itemMain1.at28 = 2;
+ }
+ else
+ {
+ itemMain1.at24 = &menuEpisode;
+ itemMain1.at28 = -1;
+ }
+ if (gGameOptions.nGameType > 0)
+ {
+ itemMainSave1.at24 = &menuNetStart;
+ itemMainSave1.at28 = 2;
+ }
+ else
+ {
+ itemMainSave1.at24 = &menuEpisode;
+ itemMainSave1.at28 = -1;
+ }
+}
+
+void SetDoppler(CGameMenuItemZBool *pItem)
+{
+ gDoppler = pItem->at20;
+}
+
+void SetCrosshair(CGameMenuItemZBool *pItem)
+{
+ gAimReticle = pItem->at20;
+}
+
+void SetCenterHoriz(CGameMenuItemZBool *pItem)
+{
+ gCenterHoriz = pItem->at20;
+}
+
+void ResetKeys(CGameMenuItemChain *)
+{
+ CONFIG_SetDefaultKeys(keydefaults);
+}
+
+void ResetKeysClassic(CGameMenuItemChain *)
+{
+ CONFIG_SetDefaultKeys(oldkeydefaults);
+}
+
+void SetShowWeapons(CGameMenuItemZBool *pItem)
+{
+ gShowWeapon = pItem->at20;
+}
+
+void SetSlopeTilting(CGameMenuItemZBool *pItem)
+{
+ gSlopeTilting = pItem->at20;
+}
+
+void SetViewBobbing(CGameMenuItemZBool *pItem)
+{
+ gViewVBobbing = pItem->at20;
+}
+
+void SetViewSwaying(CGameMenuItemZBool *pItem)
+{
+ gViewHBobbing = pItem->at20;
+}
+
+void SetDetail(CGameMenuItemSlider *pItem)
+{
+ gDetail = pItem->nValue;
+}
+
+void SetGamma(CGameMenuItemSlider *pItem)
+{
+ gGamma = pItem->nValue;
+ scrSetGamma(gGamma);
+}
+
+void SetMusicVol(CGameMenuItemSlider *pItem)
+{
+ sndSetMusicVolume(pItem->nValue);
+}
+
+void SetSoundVol(CGameMenuItemSlider *pItem)
+{
+ sndSetFXVolume(pItem->nValue);
+}
+
+void SetCDVol(CGameMenuItemSlider *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ // NUKE-TODO:
+}
+
+void SetMessages(CGameMenuItemZBool *pItem)
+{
+ gMessageState = pItem->at20;
+ gGameMessageMgr.SetState(gMessageState);
+}
+
+void SetMouseSensitivity(CGameMenuItemSliderFloat *pItem)
+{
+ CONTROL_MouseSensitivity = pItem->fValue;
+}
+
+void SetMouseAimFlipped(CGameMenuItemZBool *pItem)
+{
+ gMouseAimingFlipped = pItem->at20;
+}
+
+void SetTurnSpeed(CGameMenuItemSlider *pItem)
+{
+ gTurnSpeed = pItem->nValue;
+}
+
+void SetAutoAim(CGameMenuItemZCycle *pItem)
+{
+ gAutoAim = pItem->m_nFocus;
+ if (!gDemo.at0 && !gDemo.at1)
+ {
+ gProfile[myconnectindex].nAutoAim = gAutoAim;
+ netBroadcastPlayerInfo(myconnectindex);
+ }
+}
+
+void SetLevelStats(CGameMenuItemZBool *pItem)
+{
+ gLevelStats = pItem->at20;
+}
+
+void SetPowerupDuration(CGameMenuItemZBool* pItem)
+{
+ gPowerupDuration = pItem->at20;
+}
+
+void SetShowMapTitle(CGameMenuItemZBool* pItem)
+{
+ gShowMapTitle = pItem->at20;
+}
+
+void SetWeaponSwitch(CGameMenuItemZCycle *pItem)
+{
+ gWeaponSwitch &= ~(1|2);
+ switch (pItem->m_nFocus)
+ {
+ case 0:
+ break;
+ case 1:
+ gWeaponSwitch |= 2;
+ fallthrough__;
+ case 2:
+ default:
+ gWeaponSwitch |= 1;
+ break;
+ }
+ if (!gDemo.at0 && !gDemo.at1)
+ {
+ gProfile[myconnectindex].nWeaponSwitch = gWeaponSwitch;
+ netBroadcastPlayerInfo(myconnectindex);
+ }
+}
+
+extern bool gStartNewGame;
+
+void SetDifficultyAndStart(CGameMenuItemChain *pItem)
+{
+ gGameOptions.nDifficulty = pItem->at30;
+ gSkill = pItem->at30;
+ gGameOptions.nLevel = 0;
+ if (gDemo.at1)
+ gDemo.StopPlayback();
+ gStartNewGame = true;
+ gCheatMgr.sub_5BCF4();
+ gGameMenuMgr.Deactivate();
+}
+
+void SetVideoModeOld(CGameMenuItemChain *pItem)
+{
+ if (pItem->at30 == validmodecnt)
+ {
+ gSetup.fullscreen = 0;
+ gSetup.xdim = 640;
+ gSetup.ydim = 480;
+ }
+ else
+ {
+ gSetup.fullscreen = 0;
+ gSetup.xdim = validmode[pItem->at30].xdim;
+ gSetup.ydim = validmode[pItem->at30].ydim;
+ }
+ scrSetGameMode(gSetup.fullscreen, gSetup.xdim, gSetup.ydim, gSetup.bpp);
+ scrSetDac();
+ viewResizeView(gViewSize);
+}
+
+void SetVideoMode(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ resolution_t p = { xres, yres, fullscreen, bpp, 0 };
+ int32_t prend = videoGetRenderMode();
+ int32_t pvsync = vsync;
+
+ int32_t nResolution = itemOptionsDisplayModeResolution.m_nFocus;
+ resolution_t n = { gResolution[nResolution].xdim, gResolution[nResolution].ydim,
+ (gResolution[nResolution].flags & RES_FS) ? itemOptionsDisplayModeFullscreen.at20 : 0,
+ (nRendererValues[itemOptionsDisplayModeRenderer.m_nFocus] == REND_CLASSIC) ? 8 : gResolution[nResolution].bppmax, 0 };
+ int32_t UNUSED(nrend) = nRendererValues[itemOptionsDisplayModeRenderer.m_nFocus];
+ int32_t nvsync = nVSyncValues[itemOptionsDisplayModeVSync.m_nFocus];
+
+ if (videoSetGameMode(n.flags, n.xdim, n.ydim, n.bppmax, upscalefactor) < 0)
+ {
+ if (videoSetGameMode(p.flags, p.xdim, p.ydim, p.bppmax, upscalefactor) < 0)
+ {
+ videoSetRenderMode(prend);
+ ThrowError("Failed restoring old video mode.");
+ }
+ else
+ {
+ onvideomodechange(p.bppmax > 8);
+ vsync = videoSetVsync(pvsync);
+ }
+ }
+ else onvideomodechange(n.bppmax > 8);
+
+ viewResizeView(gViewSize);
+ vsync = videoSetVsync(nvsync);
+ gSetup.fullscreen = fullscreen;
+ gSetup.xdim = xres;
+ gSetup.ydim = yres;
+ gSetup.bpp = bpp;
+}
+
+void SetWidescreen(CGameMenuItemZBool *pItem)
+{
+ r_usenewaspect = pItem->at20;
+}
+
+void SetFOV(CGameMenuItemSlider *pItem)
+{
+ gFov = pItem->nValue;
+}
+
+void SetupVideoModeMenu(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ for (int i = 0; i < gResolutionNum; i++)
+ {
+ if (gSetup.xdim == gResolution[i].xdim && gSetup.ydim == gResolution[i].ydim)
+ {
+ itemOptionsDisplayModeResolution.m_nFocus = i;
+ break;
+ }
+ }
+ itemOptionsDisplayModeFullscreen.at20 = gSetup.fullscreen;
+#ifdef USE_OPENGL
+ for (int i = 0; i < 2; i++)
+ {
+ if (videoGetRenderMode() == nRendererValues[i])
+ {
+ itemOptionsDisplayModeRenderer.m_nFocus = i;
+ break;
+ }
+ }
+#endif
+ for (int i = 0; i < 3; i++)
+ {
+ if (vsync == nVSyncValues[i])
+ {
+ itemOptionsDisplayModeVSync.m_nFocus = i;
+ break;
+ }
+ }
+ for (int i = 0; i < 8; i++)
+ {
+ if (r_maxfps == nFrameLimitValues[i])
+ {
+ itemOptionsDisplayModeFrameLimit.m_nFocus = i;
+ break;
+ }
+ }
+ itemOptionsDisplayModeFPSOffset.nValue = r_maxfpsoffset;
+}
+
+void PreDrawVideoModeMenu(CGameMenuItem *pItem)
+{
+ if (pItem == &itemOptionsDisplayModeFullscreen)
+ pItem->bEnable = !!(gResolution[itemOptionsDisplayModeResolution.m_nFocus].flags & RES_FS);
+#ifdef USE_OPENGL
+ else if (pItem == &itemOptionsDisplayModeRenderer)
+ pItem->bEnable = gResolution[itemOptionsDisplayModeResolution.m_nFocus].bppmax > 8;
+#endif
+}
+
+void UpdateVideoModeMenuFrameLimit(CGameMenuItemZCycle *pItem)
+{
+ r_maxfps = nFrameLimitValues[pItem->m_nFocus];
+ g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset);
+}
+
+void UpdateVideoModeMenuFPSOffset(CGameMenuItemSlider *pItem)
+{
+ r_maxfpsoffset = pItem->nValue;
+ g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset);
+}
+
+void UpdateVideoColorMenu(CGameMenuItemSliderFloat *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ g_videoGamma = itemOptionsDisplayColorGamma.fValue;
+ g_videoContrast = itemOptionsDisplayColorContrast.fValue;
+ g_videoBrightness = itemOptionsDisplayColorBrightness.fValue;
+ r_ambientlight = itemOptionsDisplayColorVisibility.fValue;
+ r_ambientlightrecip = 1.f/r_ambientlight;
+ gBrightness = GAMMA_CALC<<2;
+ videoSetPalette(gBrightness>>2, gLastPal, 0);
+}
+
+void PreDrawDisplayColor(CGameMenuItem *pItem)
+{
+ if (pItem == &itemOptionsDisplayColorContrast)
+ pItem->bEnable = gammabrightness;
+ else if (pItem == &itemOptionsDisplayColorBrightness)
+ pItem->bEnable = gammabrightness;
+}
+
+void ResetVideoColor(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ g_videoGamma = DEFAULT_GAMMA;
+ g_videoContrast = DEFAULT_CONTRAST;
+ g_videoBrightness = DEFAULT_BRIGHTNESS;
+ gBrightness = 0;
+ r_ambientlight = r_ambientlightrecip = 1.f;
+ videoSetPalette(gBrightness>>2, gLastPal, 0);
+}
+
+#ifdef USE_OPENGL
+void SetupVideoPolymostMenu(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ itemOptionsDisplayPolymostTextureMode.m_nFocus = 0;
+ for (int i = 0; i < 2; i++)
+ {
+ if (nTextureModeValues[i] == gltexfiltermode)
+ {
+ itemOptionsDisplayPolymostTextureMode.m_nFocus = i;
+ break;
+ }
+ }
+ itemOptionsDisplayPolymostAnisotropy.m_nFocus = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ if (nAnisotropyValues[i] == glanisotropy)
+ {
+ itemOptionsDisplayPolymostAnisotropy.m_nFocus = i;
+ break;
+ }
+ }
+ itemOptionsDisplayPolymostTrueColorTextures.at20 = usehightile;
+ itemOptionsDisplayPolymostTexQuality.m_nFocus = r_downsize;
+ itemOptionsDisplayPolymostPreloadCache.at20 = useprecache;
+ itemOptionsDisplayPolymostDetailTex.at20 = r_detailmapping;
+ itemOptionsDisplayPolymostGlowTex.at20 = r_glowmapping;
+ itemOptionsDisplayPolymost3DModels.at20 = usemodels;
+ itemOptionsDisplayPolymostDeliriumBlur.at20 = gDeliriumBlur;
+}
+
+void UpdateTextureMode(CGameMenuItemZCycle *pItem)
+{
+ gltexfiltermode = nTextureModeValues[pItem->m_nFocus];
+ gltexapplyprops();
+}
+
+void UpdateAnisotropy(CGameMenuItemZCycle *pItem)
+{
+ glanisotropy = nAnisotropyValues[pItem->m_nFocus];
+ gltexapplyprops();
+}
+
+void UpdateTrueColorTextures(CGameMenuItemZBool *pItem)
+{
+ usehightile = pItem->at20;
+}
+#endif
+
+void DoModeChange(void)
+{
+ videoResetMode();
+ if (videoSetGameMode(fullscreen, xres, yres, bpp, upscalefactor))
+ OSD_Printf("restartvid: Reset failed...\n");
+ onvideomodechange(gSetup.bpp > 8);
+}
+
+#ifdef USE_OPENGL
+void UpdateTexQuality(CGameMenuItemZCycle *pItem)
+{
+ r_downsize = pItem->m_nFocus;
+ r_downsizevar = r_downsize;
+ DoModeChange();
+}
+
+void UpdatePreloadCache(CGameMenuItemZBool *pItem)
+{
+ useprecache = pItem->at20;
+}
+
+void UpdateDetailTex(CGameMenuItemZBool *pItem)
+{
+ r_detailmapping = pItem->at20;
+}
+
+void UpdateGlowTex(CGameMenuItemZBool *pItem)
+{
+ r_glowmapping = pItem->at20;
+}
+
+void Update3DModels(CGameMenuItemZBool *pItem)
+{
+ usemodels = pItem->at20;
+}
+
+void UpdateDeliriumBlur(CGameMenuItemZBool *pItem)
+{
+ gDeliriumBlur = pItem->at20;
+}
+
+void PreDrawDisplayPolymost(CGameMenuItem *pItem)
+{
+ if (pItem == &itemOptionsDisplayPolymostTexQuality)
+ pItem->bEnable = usehightile;
+ else if (pItem == &itemOptionsDisplayPolymostPreloadCache)
+ pItem->bEnable = usehightile;
+ else if (pItem == &itemOptionsDisplayPolymostDetailTex)
+ pItem->bEnable = usehightile;
+ else if (pItem == &itemOptionsDisplayPolymostGlowTex)
+ pItem->bEnable = usehightile;
+}
+#endif
+
+void UpdateSoundToggle(CGameMenuItemZBool *pItem)
+{
+ SoundToggle = pItem->at20;
+ if (!SoundToggle)
+ FX_StopAllSounds();
+}
+
+void UpdateMusicToggle(CGameMenuItemZBool *pItem)
+{
+ MusicToggle = pItem->at20;
+ if (!MusicToggle)
+ sndStopSong();
+ else
+ {
+ if (gGameStarted || gDemo.at1)
+ sndPlaySong(gGameOptions.zLevelSong, true);
+ }
+}
+
+void Update3DToggle(CGameMenuItemZBool *pItem)
+{
+ gDoppler = pItem->at20;
+}
+
+void UpdateCDToggle(CGameMenuItemZBool *pItem)
+{
+ CDAudioToggle = pItem->at20;
+ if (gGameStarted || gDemo.at1)
+ levelTryPlayMusicOrNothing(gGameOptions.nEpisode, gGameOptions.nLevel);
+}
+
+void UpdateSoundVolume(CGameMenuItemSlider *pItem)
+{
+ sndSetFXVolume(pItem->nValue);
+}
+
+void UpdateMusicVolume(CGameMenuItemSlider *pItem)
+{
+ sndSetMusicVolume(pItem->nValue);
+}
+
+void UpdateSoundRate(CGameMenuItemZCycle *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void UpdateNumVoices(CGameMenuItemSlider *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void UpdateMusicDevice(CGameMenuItemZCycle *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void SetSound(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ MixRate = nSoundRateValues[itemOptionsSoundSampleRate.m_nFocus];
+ NumVoices = itemOptionsSoundNumVoices.nValue;
+ MusicDevice = itemOptionsSoundMusicDevice.m_nFocus;
+ sfxTerm();
+ sndTerm();
+
+ sndInit();
+ sfxInit();
+
+ if (MusicToggle && (gGameStarted || gDemo.at1))
+ sndPlaySong(gGameOptions.zLevelSong, true);
+}
+
+void PreDrawSound(CGameMenuItem *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void SetupOptionsSound(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ itemOptionsSoundSoundToggle.at20 = SoundToggle;
+ itemOptionsSoundMusicToggle.at20 = MusicToggle;
+ itemOptionsSound3DToggle.at20 = gDoppler;
+ itemOptionsSoundCDToggle.at20 = CDAudioToggle;
+ itemOptionsSoundSampleRate.m_nFocus = 0;
+ for (int i = 0; i < 3; i++)
+ {
+ if (nSoundRateValues[i] == MixRate)
+ {
+ itemOptionsSoundSampleRate.m_nFocus = i;
+ break;
+ }
+ }
+ itemOptionsSoundNumVoices.nValue = NumVoices;
+ itemOptionsSoundMusicDevice.m_nFocus = MusicDevice;
+}
+
+void UpdatePlayerName(CGameMenuItemZEdit *pItem, CGameMenuEvent *pEvent)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ if (pEvent->at0 == kMenuEventEnter)
+ netBroadcastPlayerInfo(myconnectindex);
+}
+
+void SetMouseFilterInput(CGameMenuItemZBool *pItem)
+{
+ CONTROL_SmoothMouse = pItem->at20;
+ SmoothInput = pItem->at20;
+}
+
+void SetMouseAimMode(CGameMenuItemZBool *pItem)
+{
+ gMouseAiming = pItem->at20;
+}
+
+void SetMouseVerticalAim(CGameMenuItemZBool *pItem)
+{
+ gMouseAim = pItem->at20;
+}
+
+void SetMouseXScale(CGameMenuItemSlider *pItem)
+{
+ MouseAnalogueScale[0] = pItem->nValue;
+ CONTROL_SetAnalogAxisScale(0, pItem->nValue, controldevice_mouse);
+}
+
+void SetMouseYScale(CGameMenuItemSlider *pItem)
+{
+ MouseAnalogueScale[1] = pItem->nValue;
+ CONTROL_SetAnalogAxisScale(1, pItem->nValue, controldevice_mouse);
+}
+
+void SetMouseDigitalAxis(CGameMenuItemZCycle *pItem)
+{
+ if (pItem == &itemOptionsControlMouseDigitalUp)
+ {
+ MouseDigitalFunctions[1][0] = nGamefuncsValues[pItem->m_nFocus];
+ CONTROL_MapDigitalAxis(1, MouseDigitalFunctions[1][0], 0, controldevice_mouse);
+ }
+ else if (pItem == &itemOptionsControlMouseDigitalDown)
+ {
+ MouseDigitalFunctions[1][1] = nGamefuncsValues[pItem->m_nFocus];
+ CONTROL_MapDigitalAxis(1, MouseDigitalFunctions[1][1], 1, controldevice_mouse);
+ }
+ else if (pItem == &itemOptionsControlMouseDigitalLeft)
+ {
+ MouseDigitalFunctions[0][0] = nGamefuncsValues[pItem->m_nFocus];
+ CONTROL_MapDigitalAxis(0, MouseDigitalFunctions[0][0], 0, controldevice_mouse);
+ }
+ else if (pItem == &itemOptionsControlMouseDigitalRight)
+ {
+ MouseDigitalFunctions[0][1] = nGamefuncsValues[pItem->m_nFocus];
+ CONTROL_MapDigitalAxis(0, MouseDigitalFunctions[0][1], 1, controldevice_mouse);
+ }
+}
+
+void SetupMouseMenu(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ static CGameMenuItemZCycle *pMouseDigitalAxis[4] = {
+ &itemOptionsControlMouseDigitalLeft,
+ &itemOptionsControlMouseDigitalRight,
+ &itemOptionsControlMouseDigitalUp,
+ &itemOptionsControlMouseDigitalDown
+ };
+ for (int i = 0; i < ARRAY_SSIZE(pMouseDigitalAxis); i++)
+ {
+ CGameMenuItemZCycle *pItem = pMouseDigitalAxis[i];
+ pItem->m_nFocus = 0;
+ for (int j = 0; j < NUMGAMEFUNCTIONS+1; j++)
+ {
+ if (nGamefuncsValues[j] == MouseDigitalFunctions[i>>1][i&1])
+ {
+ pItem->m_nFocus = j;
+ break;
+ }
+ }
+ }
+ itemOptionsControlMouseAimFlipped.at20 = gMouseAimingFlipped;
+ itemOptionsControlMouseFilterInput.at20 = SmoothInput;
+ itemOptionsControlMouseAimMode.at20 = gMouseAiming;
+ itemOptionsControlMouseVerticalAim.at20 = gMouseAim;
+}
+
+void PreDrawControlMouse(CGameMenuItem *pItem)
+{
+ if (pItem == &itemOptionsControlMouseVerticalAim)
+ pItem->bEnable = !gMouseAiming;
+}
+
+void SetMouseButton(CGameMenuItemZCycle *pItem)
+{
+ for (int i = 0; i < MENUMOUSEFUNCTIONS; i++)
+ {
+ if (pItem == pItemOptionsControlMouseButton[i])
+ {
+ int nFunc = nGamefuncsValues[pItem->m_nFocus];
+ MouseFunctions[MenuMouseDataIndex[i][0]][MenuMouseDataIndex[i][1]] = nFunc;
+ CONTROL_MapButton(nFunc, MenuMouseDataIndex[i][0], MenuMouseDataIndex[i][1], controldevice_mouse);
+ CONTROL_FreeMouseBind(MenuMouseDataIndex[i][0]);
+ break;
+ }
+ }
+}
+
+void SetupMouseButtonMenu(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ for (int i = 0; i < MENUMOUSEFUNCTIONS; i++)
+ {
+ auto pItem = pItemOptionsControlMouseButton[i];
+ pItem->m_nFocus = 0;
+ for (int j = 0; j < NUMGAMEFUNCTIONS+1; j++)
+ {
+ if (MouseFunctions[MenuMouseDataIndex[i][0]][MenuMouseDataIndex[i][1]] == nGamefuncsValues[j])
+ {
+ pItem->m_nFocus = j;
+ break;
+ }
+ }
+ }
+}
+
+void SetupNetworkMenu(void)
+{
+ sprintf(zNetPortBuffer, "%d", gNetPort);
+ if (strlen(gNetAddress) > 0)
+ strncpy(zNetAddressBuffer, gNetAddress, sizeof(zNetAddressBuffer)-1);
+
+ menuNetwork.Add(&itemNetworkTitle, false);
+ menuNetwork.Add(&itemNetworkHost, true);
+ menuNetwork.Add(&itemNetworkJoin, false);
+ menuNetwork.Add(&itemBloodQAV, false);
+
+ menuNetworkHost.Add(&itemNetworkHostTitle, false);
+ menuNetworkHost.Add(&itemNetworkHostPlayerNum, true);
+ menuNetworkHost.Add(&itemNetworkHostPort, false);
+ menuNetworkHost.Add(&itemNetworkHostHost, false);
+ menuNetworkHost.Add(&itemBloodQAV, false);
+
+ menuNetworkJoin.Add(&itemNetworkJoinTitle, false);
+ menuNetworkJoin.Add(&itemNetworkJoinAddress, true);
+ menuNetworkJoin.Add(&itemNetworkJoinPort, false);
+ menuNetworkJoin.Add(&itemNetworkJoinJoin, false);
+ menuNetworkJoin.Add(&itemBloodQAV, false);
+}
+
+void SetupNetworkHostMenu(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void SetupNetworkJoinMenu(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void NetworkHostGame(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ sndStopSong();
+ FX_StopAllSounds();
+ UpdateDacs(0, true);
+ gNetPlayers = itemNetworkHostPlayerNum.nValue;
+ gNetPort = strtoul(zNetPortBuffer, NULL, 10);
+ if (!gNetPort)
+ gNetPort = kNetDefaultPort;
+ gNetMode = NETWORK_SERVER;
+ netInitialize(false);
+ gGameMenuMgr.Deactivate();
+ gQuitGame = gRestartGame = true;
+}
+
+void NetworkJoinGame(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ sndStopSong();
+ FX_StopAllSounds();
+ UpdateDacs(0, true);
+ strcpy(gNetAddress, zNetAddressBuffer);
+ gNetPort = strtoul(zNetPortBuffer, NULL, 10);
+ if (!gNetPort)
+ gNetPort = kNetDefaultPort;
+ gNetMode = NETWORK_CLIENT;
+ netInitialize(false);
+ gGameMenuMgr.Deactivate();
+ gQuitGame = gRestartGame = true;
+}
+
+void SaveGameProcess(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+void TenProcess(CGameMenuItem7EA1C *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+}
+
+short gQuickLoadSlot = -1;
+short gQuickSaveSlot = -1;
+
+void SaveGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
+{
+ char strSaveGameName[BMAX_PATH];
+ int nSlot = pItem->at28;
+ if (gGameOptions.nGameType > 0 || !gGameStarted)
+ return;
+ if (event->at0 != 6/* || strSaveGameName[0]*/)
+ {
+ gGameMenuMgr.Deactivate();
+ return;
+ }
+ G_ModDirSnprintf(strSaveGameName, BMAX_PATH, "game00%02d.sav", nSlot);
+ strcpy(gGameOptions.szUserGameName, strRestoreGameStrings[nSlot]);
+ sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName);
+ gGameOptions.nSaveGameSlot = nSlot;
+ viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[nSlot]);
+ gSaveGameNum = nSlot;
+ LoadSave::SaveGame(strSaveGameName);
+ gQuickSaveSlot = nSlot;
+ gGameMenuMgr.Deactivate();
+}
+
+void QuickSaveGame(void)
+{
+ char strSaveGameName[BMAX_PATH];
+ if (gGameOptions.nGameType > 0 || !gGameStarted)
+ return;
+ /*if (strSaveGameName[0])
+ {
+ gGameMenuMgr.Deactivate();
+ return;
+ }*/
+ G_ModDirSnprintf(strSaveGameName, BMAX_PATH, "game00%02d.sav", gQuickSaveSlot);
+ strcpy(gGameOptions.szUserGameName, strRestoreGameStrings[gQuickSaveSlot]);
+ sprintf(gGameOptions.szSaveGameName, "%s", strSaveGameName);
+ gGameOptions.nSaveGameSlot = gQuickSaveSlot;
+ viewLoadingScreen(2518, "Saving", "Saving Your Game", strRestoreGameStrings[gQuickSaveSlot]);
+ LoadSave::SaveGame(strSaveGameName);
+ gGameOptions.picEntry = gSavedOffset;
+ gSaveGameOptions[gQuickSaveSlot] = gGameOptions;
+ UpdateSavedInfo(gQuickSaveSlot);
+ gGameMenuMgr.Deactivate();
+}
+
+void LoadGame(CGameMenuItemZEditBitmap *pItem, CGameMenuEvent *event)
+{
+ UNREFERENCED_PARAMETER(event);
+ char strLoadGameName[BMAX_PATH];
+ int nSlot = pItem->at28;
+ if (gGameOptions.nGameType > 0)
+ return;
+ G_ModDirSnprintf(strLoadGameName, BMAX_PATH, "game00%02d.sav", nSlot);
+ if (!testkopen(strLoadGameName, 0))
+ return;
+ viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[nSlot]);
+ LoadSave::LoadGame(strLoadGameName);
+ gGameMenuMgr.Deactivate();
+ gQuickLoadSlot = nSlot;
+}
+
+void QuickLoadGame(void)
+{
+ char strLoadGameName[BMAX_PATH];
+ if (gGameOptions.nGameType > 0)
+ return;
+ G_ModDirSnprintf(strLoadGameName, BMAX_PATH, "game00%02d.sav", gQuickLoadSlot);
+ if (!testkopen(strLoadGameName, 0))
+ return;
+ viewLoadingScreen(2518, "Loading", "Loading Saved Game", strRestoreGameStrings[gQuickLoadSlot]);
+ LoadSave::LoadGame(strLoadGameName);
+ gGameMenuMgr.Deactivate();
+}
+
+void SetupLevelMenuItem(int nEpisode)
+{
+ dassert(nEpisode >= 0 && nEpisode < gEpisodeCount);
+ itemNetStart3.SetTextArray(zLevelNames[nEpisode], gEpisodeInfo[nEpisode].nLevels, 0);
+}
+
+void SetupNetLevels(CGameMenuItemZCycle *pItem)
+{
+ SetupLevelMenuItem(pItem->m_nFocus);
+}
+
+void StartNetGame(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ gPacketStartGame.gameType = itemNetStart1.m_nFocus+1;
+ if (gPacketStartGame.gameType == 0)
+ gPacketStartGame.gameType = 2;
+ gPacketStartGame.episodeId = itemNetStart2.m_nFocus;
+ gPacketStartGame.levelId = itemNetStart3.m_nFocus;
+ gPacketStartGame.difficulty = itemNetStart4.m_nFocus;
+ gPacketStartGame.monsterSettings = itemNetStart5.m_nFocus;
+ gPacketStartGame.weaponSettings = itemNetStart6.m_nFocus;
+ gPacketStartGame.itemSettings = itemNetStart7.m_nFocus;
+ gPacketStartGame.respawnSettings = 0;
+ gPacketStartGame.unk = 0;
+ gPacketStartGame.userMapName[0] = 0;
+ strncpy(gPacketStartGame.userMapName, itemNetStart9.at20, 13);
+ gPacketStartGame.userMapName[12] = 0;
+ gPacketStartGame.userMap = gPacketStartGame.userMapName[0] != 0;
+ netBroadcastNewGame();
+ gStartNewGame = 1;
+ gGameMenuMgr.Deactivate();
+}
+
+void Restart(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ if (gGameOptions.nGameType == 0 || numplayers == 1)
+ {
+ gQuitGame = true;
+ gRestartGame = true;
+ }
+ else
+ gQuitRequest = 2;
+ gGameMenuMgr.Deactivate();
+}
+
+void Quit(CGameMenuItemChain *pItem)
+{
+ UNREFERENCED_PARAMETER(pItem);
+ if (gGameOptions.nGameType == 0 || numplayers == 1)
+ gQuitGame = true;
+ else
+ gQuitRequest = 1;
+ gGameMenuMgr.Deactivate();
+}
+
+void SetParentalLock(CGameMenuItemZBool *pItem)
+{
+ if (!pItem->at20)
+ {
+ pItem->at20 = true;
+ pItem->Draw();
+ if (strcmp(itemParentalLockPassword.at20, ""))
+ {
+ itemParentalLockPassword.pMenu->FocusNextItem();
+ itemParentalLockPassword.at32 = 0;
+ itemParentalLockPassword.at37 = 1;
+ itemParentalLockPassword.at5f = pItem;
+ itemParentalLockPassword.at29[0] = 0;
+ return;
+ }
+ else
+ {
+ itemParentalLockPassword.at20[0] = 0;
+ pItem->Draw();
+ gbAdultContent = false;
+ }
+ }
+ else
+ gbAdultContent = true;
+ // NUKE-TODO: CONFIG_WriteAdultMode();
+}
+
+void MenuSetupEpisodeInfo(void)
+{
+ memset(zEpisodeNames, 0, sizeof(zEpisodeNames));
+ memset(zLevelNames, 0, sizeof(zLevelNames));
+ for (int i = 0; i < 6; i++)
+ {
+ if (i < gEpisodeCount)
+ {
+ EPISODEINFO *pEpisode = &gEpisodeInfo[i];
+ zEpisodeNames[i] = pEpisode->at0;
+ for (int j = 0; j < 16; j++)
+ {
+ if (j < pEpisode->nLevels)
+ {
+ zLevelNames[i][j] = pEpisode->at28[j].at90;
+ }
+ }
+ }
+ }
+}
+
+void drawLoadingScreen(void)
+{
+ char buffer[80];
+ if (gGameOptions.nGameType == 0)
+ {
+ if (gDemo.at1)
+ sprintf(buffer, "Loading Demo");
+ else
+ sprintf(buffer, "Loading Level");
+ }
+ else
+ sprintf(buffer, "%s", zNetGameTypes[gGameOptions.nGameType-1]);
+ viewLoadingScreen(2049, buffer, levelGetTitle(), NULL);
+}
diff --git a/source/blood/src/menu.h b/source/blood/src/menu.h
new file mode 100644
index 000000000..bfa447e88
--- /dev/null
+++ b/source/blood/src/menu.h
@@ -0,0 +1,58 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "gamemenu.h"
+
+extern CGameMenu menuMain;
+extern CGameMenu menuMainWithSave;
+extern CGameMenu menuNetMain;
+extern CGameMenu menuNetStart;
+extern CGameMenu menuEpisode;
+extern CGameMenu menuDifficulty;
+extern CGameMenu menuOptionsOld;
+extern CGameMenu menuControls;
+extern CGameMenu menuMessages;
+extern CGameMenu menuKeys;
+extern CGameMenu menuSaveGame;
+extern CGameMenu menuLoadGame;
+extern CGameMenu menuLoading;
+extern CGameMenu menuSounds;
+extern CGameMenu menuQuit;
+extern CGameMenu menuRestart;
+extern CGameMenu menuCredits;
+extern CGameMenu menuOrder;
+extern CGameMenu menuPlayOnline;
+extern CGameMenu menuParentalLock;
+extern CGameMenu menuSorry;
+extern CGameMenu menuSorry2;
+
+extern CGameMenu menuOptions;
+extern CGameMenu menuOptionsSound;
+extern short gQuickLoadSlot;
+extern short gQuickSaveSlot;
+extern char strRestoreGameStrings[][16];
+void drawLoadingScreen(void);
+void SetupMenus(void);
+void UpdateNetworkMenus(void);
+void QuickSaveGame(void);
+void QuickLoadGame(void);
\ No newline at end of file
diff --git a/source/blood/src/messages.cpp b/source/blood/src/messages.cpp
new file mode 100644
index 000000000..91d8e001c
--- /dev/null
+++ b/source/blood/src/messages.cpp
@@ -0,0 +1,821 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "mmulti.h"
+#include "compat.h"
+#include "keyboard.h"
+#include "control.h"
+#include "function.h"
+#include "common_game.h"
+#include "blood.h"
+#include "config.h"
+#include "demo.h"
+#include "eventq.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "menu.h"
+#include "messages.h"
+#include "network.h"
+#include "player.h"
+#include "view.h"
+
+CPlayerMsg gPlayerMsg;
+CCheatMgr gCheatMgr;
+
+void sub_5A928(void)
+{
+ for (int i = 0; i < NUMGAMEFUNCTIONS-1; i++)
+ CONTROL_ClearButton(i);
+}
+
+extern uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2];
+void sub_5A944(char key)
+{
+ for (int i = 0; i < NUMGAMEFUNCTIONS-1; i++)
+ {
+ char key1, key2;
+ key1 = KeyboardKeys[i][0];
+ key2 = KeyboardKeys[i][1];
+ if (key1 == key || key2 == key)
+ CONTROL_ClearButton(i);
+ }
+}
+
+void SetGodMode(bool god)
+{
+ playerSetGodMode(gMe, god);
+ if (gMe->at31a)
+ viewSetMessage("You are immortal.");
+ else
+ viewSetMessage("You are mortal.");
+}
+
+void SetClipMode(bool noclip)
+{
+ gNoClip = noclip;
+ if (gNoClip)
+ viewSetMessage("Unclipped movement.");
+ else
+ viewSetMessage("Normal movement.");
+}
+
+void packStuff(PLAYER *pPlayer)
+{
+ for (int i = 0; i < 5; i++)
+ packAddItem(pPlayer, i);
+}
+
+void packClear(PLAYER *pPlayer)
+{
+ pPlayer->at321 = 0;
+ for (int i = 0; i < 5; i++)
+ {
+ pPlayer->packInfo[i].at0 = 0;
+ pPlayer->packInfo[i].at1 = 0;
+ }
+}
+
+void SetAmmo(bool stat)
+{
+ if (stat)
+ {
+ for (int i = 0; i < 12; i++)
+ gMe->at181[i] = gAmmoInfo[i].at0;
+ viewSetMessage("You have full ammo.");
+ }
+ else
+ {
+ for (int i = 0; i < 12; i++)
+ gMe->at181[i] = 0;
+ viewSetMessage("You have no ammo.");
+ }
+}
+
+void SetWeapons(bool stat)
+{
+ for (int i = 0; i < 14; i++)
+ {
+ gMe->atcb[i] = stat;
+ }
+ SetAmmo(stat);
+ if (stat)
+ viewSetMessage("You have all weapons.");
+ else
+ {
+ if (!VanillaMode())
+ {
+ // Keep the pitchfork to avoid freeze
+ gMe->atcb[1] = 1;
+ gMe->atbd = 0;
+ gMe->atbe = 1;
+ }
+ viewSetMessage("You have no weapons.");
+ }
+}
+
+void SetToys(bool stat)
+{
+ if (stat)
+ {
+ packStuff(gMe);
+ viewSetMessage("Your inventory is full.");
+ }
+ else
+ {
+ packClear(gMe);
+ viewSetMessage("Your inventory is empty.");
+ }
+}
+
+void SetArmor(bool stat)
+{
+ int nAmount;
+ if (stat)
+ {
+ viewSetMessage("You have full armor.");
+ nAmount = 3200;
+ }
+ else
+ {
+ viewSetMessage("You have no armor.");
+ nAmount = 0;
+ }
+ for (int i = 0; i < 3; i++)
+ gMe->at33e[i] = nAmount;
+}
+
+void SetKeys(bool stat)
+{
+ for (int i = 1; i <= 6; i++)
+ gMe->at88[i] = stat;
+ if (stat)
+ viewSetMessage("You have all keys.");
+ else
+ viewSetMessage("You have no keys.");
+}
+
+void SetInfiniteAmmo(bool stat)
+{
+ gInfiniteAmmo = stat;
+ if (gInfiniteAmmo)
+ viewSetMessage("You have infinite ammo.");
+ else
+ viewSetMessage("You have limited ammo.");
+}
+
+void SetMap(bool stat)
+{
+ gFullMap = stat;
+ if (gFullMap)
+ viewSetMessage("You have the map.");
+ else
+ viewSetMessage("You have no map.");
+}
+
+void SetWooMode(bool stat)
+{
+ if (stat)
+ {
+ if (!powerupCheck(gMe, 17))
+ powerupActivate(gMe, 17);
+ }
+ else
+ {
+ if (powerupCheck(gMe, 17))
+ {
+ if (!VanillaMode())
+ gMe->at202[17] = 0;
+ powerupDeactivate(gMe, 17);
+ }
+ }
+}
+
+void ToggleWooMode(void)
+{
+ SetWooMode(!(powerupCheck(gMe, 17) != 0));
+}
+
+void ToggleBoots(void)
+{
+ if (powerupCheck(gMe, 15))
+ {
+ viewSetMessage("You have no Jumping Boots.");
+ if (!VanillaMode())
+ {
+ gMe->at202[15] = 0;
+ gMe->packInfo[4].at1 = 0;
+ }
+ powerupDeactivate(gMe, 15);
+ }
+ else
+ {
+ viewSetMessage("You have the Jumping Boots.");
+ if (!VanillaMode())
+ gMe->at202[15] = gPowerUpInfo[15].at3;
+ powerupActivate(gMe, 15);
+ }
+}
+
+void ToggleInvisibility(void)
+{
+ if (powerupCheck(gMe, 13))
+ {
+ viewSetMessage("You are visible.");
+ if (!VanillaMode())
+ gMe->at202[13] = 0;
+ powerupDeactivate(gMe, 13);
+ }
+ else
+ {
+ viewSetMessage("You are invisible.");
+ powerupActivate(gMe, 13);
+ }
+}
+
+void ToggleInvulnerability(void)
+{
+ if (powerupCheck(gMe, 14))
+ {
+ viewSetMessage("You are vulnerable.");
+ if (!VanillaMode())
+ gMe->at202[14] = 0;
+ powerupDeactivate(gMe, 14);
+ }
+ else
+ {
+ viewSetMessage("You are invulnerable.");
+ powerupActivate(gMe, 14);
+ }
+}
+
+void ToggleDelirium(void)
+{
+ if (powerupCheck(gMe, 28))
+ {
+ viewSetMessage("You are not delirious.");
+ if (!VanillaMode())
+ gMe->at202[28] = 0;
+ powerupDeactivate(gMe, 28);
+ }
+ else
+ {
+ viewSetMessage("You are delirious.");
+ powerupActivate(gMe, 28);
+ }
+}
+
+void LevelWarp(int nEpisode, int nLevel)
+{
+ levelSetupOptions(nEpisode, nLevel);
+ StartLevel(&gGameOptions);
+ viewResizeView(gViewSize);
+}
+
+void LevelWarpAndRecord(int nEpisode, int nLevel)
+{
+ char buffer[BMAX_PATH];
+ levelSetupOptions(nEpisode, nLevel);
+ gGameStarted = false;
+ strcpy(buffer, levelGetFilename(nEpisode, nLevel));
+ ChangeExtension(buffer, ".DEM");
+ gDemo.Create(buffer);
+ StartLevel(&gGameOptions);
+ viewResizeView(gViewSize);
+}
+
+CGameMessageMgr::CGameMessageMgr()
+{
+ at1 = 1;
+ at5 = 0;
+ at9 = 0;
+ atd = 0;
+ at11 = 0;
+ at15 = 8;
+ at19 = 4;
+ at1d = 5;
+ at21 = 15;
+ at22 = 0;
+ at2a = at26 = 0;
+}
+
+void CGameMessageMgr::SetState(char state)
+{
+ if (at0 && !state)
+ {
+ at0 = 0;
+ Clear();
+ }
+ else if (!at0 && state)
+ at0 = 1;
+}
+
+void CGameMessageMgr::Add(const char *a1, char a2)
+{
+ if (a2 && at21)
+ {
+ messageStruct *pMessage = &at2e[at2a];
+ strncpy(pMessage->at4, a1, 80);
+ pMessage->at4[80] = 0;
+ pMessage->at0 = gFrameClock + at1d*120;
+ at2a = (at2a+1)%16;
+ at22++;
+ if (at22 > at19)
+ {
+ at26 = (at26+1)%16;
+ atd = 0;
+ at22 = at19;
+ at9 = at15;
+ }
+ }
+}
+
+void CGameMessageMgr::Display(void)
+{
+ if (at22 && at0 && gInputMode != INPUT_MODE_2)
+ {
+ int v10 = at22;
+ int v18 = at26;
+ int vc = ClipHigh(v10*8, 48);
+ int v14 = gViewMode == 3 ? gViewX0S : 0;
+ int v8 = (gViewMode == 3 ? at5 : 0) + at9;
+ for (int i = 0; i < v10; i++)
+ {
+ messageStruct *pMessage = &at2e[(v18+i)%16];
+ if (pMessage->at0 < gFrameClock)
+ {
+ at26 = (at26+1)%16;
+ at22--;
+ continue;
+ }
+ viewDrawText(at11, pMessage->at4, v14, v8, vc, 0, 0, false, 256);
+ if (gViewMode == 3)
+ {
+ int height;
+ gMenuTextMgr.GetFontInfo(at11, pMessage->at4, &height, NULL);
+ if (v14+height > gViewX1S)
+ viewUpdatePages();
+ }
+ v8 += at15;
+ vc = ClipLow(vc-64/v10, -128);
+ }
+ if (at9)
+ {
+ at9 = at15*at9/120;
+ atd += gFrameTicks;
+ }
+ }
+}
+
+void CGameMessageMgr::Clear(void)
+{
+ at26 = at2a = at22 = 0;
+}
+
+void CGameMessageMgr::SetMaxMessages(int nMessages)
+{
+ at19 = ClipRange(nMessages, 1, 16);
+}
+
+void CGameMessageMgr::SetFont(int nFont)
+{
+ at11 = nFont;
+ at15 = gFont[nFont].ySize;
+}
+
+void CGameMessageMgr::SetCoordinates(int x, int y)
+{
+ at1 = ClipRange(x, 0, gViewX1S);
+ at5 = ClipRange(y, 0, gViewY1S);
+}
+
+void CGameMessageMgr::SetMessageTime(int nTime)
+{
+ at1d = ClipRange(nTime, 1, 8);
+}
+
+void CGameMessageMgr::SetMessageFlags(unsigned int nFlags)
+{
+ at21 = nFlags&0xf;
+}
+
+void CPlayerMsg::Clear(void)
+{
+ at4[0] = 0;
+ at0 = 0;
+}
+
+void CPlayerMsg::Term(void)
+{
+ Clear();
+ gInputMode = INPUT_MODE_0;
+}
+
+void CPlayerMsg::Draw(void)
+{
+ char buffer[44];
+ strcpy(buffer, at4);
+ if (gGameClock & 16)
+ strcat(buffer, "_");
+ int x = gViewMode == 3 ? gViewX0S : 0;
+ int y = gViewMode == 3 ? gViewY0S : 0;
+ if (gViewSize >= 1)
+ y += tilesiz[2229].y*((gNetPlayers+3)/4);
+ viewDrawText(0, buffer, x+1,y+1, -128, 0, 0, false, 256);
+ viewUpdatePages();
+}
+
+bool CPlayerMsg::AddChar(char ch)
+{
+ if (at0 < 40)
+ {
+ at4[at0++] = ch;
+ at4[at0] = 0;
+ return true;
+ }
+ return false;
+}
+
+void CPlayerMsg::DelChar(void)
+{
+ if (at0 > 0)
+ at4[--at0] = 0;
+}
+
+void CPlayerMsg::Set(const char * pzString)
+{
+ strncpy(at4, pzString, 40);
+ at0 = ClipHigh(strlen(pzString), 40);
+ at4[at0] = 0;
+}
+
+void CPlayerMsg::Send(void)
+{
+ netBroadcastMessage(myconnectindex, at4);
+ viewSetMessage(at4);
+ Term();
+ keyFlushScans();
+}
+
+void CPlayerMsg::ProcessKeys(void)
+{
+ int key = keyGetScan();
+ char ch;
+ if (key != 0)
+ {
+ bool UNUSED(alt) = keystatus[sc_LeftAlt] || keystatus[sc_RightAlt];
+ bool ctrl = keystatus[sc_LeftControl] || keystatus[sc_RightControl];
+ bool shift = keystatus[sc_LeftShift] || keystatus[sc_RightShift];
+ switch (key)
+ {
+ case sc_Escape:
+ Term();
+ break;
+ case sc_F1:
+ case sc_F2:
+ case sc_F3:
+ case sc_F4:
+ case sc_F5:
+ case sc_F6:
+ case sc_F7:
+ case sc_F8:
+ case sc_F9:
+ case sc_F10:
+ CONTROL_ClearButton(gamefunc_See_Chase_View);
+ Set(CommbatMacro[key-sc_F1]);
+ Send();
+ keystatus[key] = 0;
+ break;
+ case sc_BackSpace:
+ if (ctrl)
+ Clear();
+ else
+ DelChar();
+ break;
+ case sc_Enter:
+ case sc_kpad_Enter:
+ if (gCheatMgr.Check(at4))
+ Term();
+ else
+ Send();
+ break;
+ default:
+ if (key < 128)
+ {
+ ch = shift ? g_keyAsciiTableShift[key] : g_keyAsciiTable[key];
+ if (ch)
+ AddChar(ch);
+ }
+ break;
+ }
+ sub_5A944(key);
+ }
+}
+
+CCheatMgr::CHEATINFO CCheatMgr::s_CheatInfo[] = {
+ {"NQLGB", kCheatMpkfa, 0 }, // MPKFA (Invincibility)
+ {"DBQJONZBTT", kCheatCapInMyAss, 0 }, // CAPINMYASS (Disable invincibility )
+ {"OPDBQJONZBTT", kCheatNoCapInMyAss, 0 }, // NOCAPINMYASS (Invincibility)
+ {"J!XBOOB!CF!MJLF!LFWJO", kCheatNoCapInMyAss, 0 }, // I WANNA BE LIKE KEVIN (Invincibility)
+ {"JEBIP", kCheatIdaho, 0 }, // IDAHO (All weapons and full ammo)
+ {"NPOUBOB", kCheatMontana, 0 }, // MONTANA (All weapons, full ammo and all items)
+ {"HSJTXPME", kCheatGriswold, 0 }, // GRISWOLD (Full armor (same effect as getting super armor))
+ {"FENBSL", kCheatEdmark, 0 }, // EDMARK (Does a lot of fire damage to you (if you have 200HP and 200 fire armor then you can survive). Displays the message "THOSE WERE THE DAYS".)
+ {"UFRVJMB", kCheatTequila, 0 }, // TEQUILA (Guns akimbo power-up)
+ {"CVO[", kCheatBunz, 0 }, // BUNZ (All weapons, full ammo, and guns akimbo power-up)
+ {"GVOLZ!TIPFT", kCheatFunkyShoes, 0 }, // FUNKY SHOES (Gives jump boots item and activates it)
+ {"HBUFLFFQFS", kCheatGateKeeper, 0 }, // GATEKEEPER (Sets the you cheated flag to true, at the end of the level you will see that you have cheated)
+ {"LFZNBTUFS", kCheatKeyMaster, 0 }, // KEYMASTER (All keys)
+ {"KPKP", kCheatJoJo, 0 }, // JOJO (Drunk mode (same effect as getting bitten by red spider))
+ {"TBUDIFM", kCheatSatchel, 0 }, // SATCHEL (Full inventory)
+ {"TQPSL", kCheatSpork, 0 }, // SPORK (200% health (same effect as getting life seed))
+ {"POFSJOH", kCheatOneRing, 0 }, // ONERING (Cloak of invisibility power-up)
+ {"NBSJP", kCheatMario, 1 }, // MARIO (Warp to level E M, e.g.: MARIO 1 3 will take you to Phantom Express)
+ {"DBMHPO", kCheatCalgon, 1 }, // CALGON (Jumps to next level or can be used like MARIO with parameters)
+ {"LFWPSLJBO", kCheatKevorkian, 0 }, // KEVORKIAN (Does a lot of physical damage to you (if you have 200HP and 200 fire armor then you can survive). Displays the message "KEVORKIAN APPROVES".)
+ {"NDHFF", kCheatMcGee, 0 }, // MCGEE (Sets you on fire. Displays the message "YOU'RE FIRED".)
+ {"LSVFHFS", kCheatKrueger, 0 }, // KRUEGER (200% health, but sets you on fire. Displays the message "FLAME RETARDANT".)
+ {"DIFFTFIFBE", kCheatCheeseHead, 0 }, // CHEESEHEAD (100% diving suit)
+ {"DPVTUFBV", kCheatCousteau, 0 }, // COUSTEAU (200% health and diving suit)
+ {"WPPSIFFT", kCheatVoorhees, 0 }, // VOORHEES (Death mask power-up)
+ {"MBSB!DSPGU", kCheatLaraCroft, 0 }, // LARA CROFT (All weapons and infinite ammo. Displays the message "LARA RULES". Typing it the second time will lose all weapons and ammo.)
+ {"IPOHLPOH", kCheatHongKong, 0 }, // HONGKONG (All weapons and infinite ammo)
+ {"GSBOLFOTUFJO", kCheatFrankenstein, 0 }, // FRANKENSTEIN (100% med-kit)
+ {"TUFSOP", kCheatSterno, 0 }, // STERNO (Temporary blindness (same effect as getting bitten by green spider))
+ {"DMBSJDF", kCheatClarice, 0 }, // CLARICE (Gives 100% body armor, 100% fire armor, 100% spirit armor)
+ {"GPSL!ZPV", kCheatForkYou, 0 }, // FORK YOU (Drunk mode, 1HP, no armor, no weapons, no ammo, no items, no keys, no map, guns akimbo power-up)
+ {"MJFCFSNBO", kCheatLieberMan, 0 }, // LIEBERMAN (Sets the you cheated flag to true, at the end of the level you will see that you have cheated)
+ {"FWB!HBMMJ", kCheatEvaGalli, 0 }, // EVA GALLI (Disable/enable clipping (grant the ability to walk through walls))
+ {"SBUF", kCheatRate, 0 }, // RATE (Display frame rate (doesn't count as a cheat))
+ {"HPPOJFT", kCheatGoonies, 0 }, // GOONIES (Enable full map. Displays the message "YOU HAVE THE MAP".)
+ {"TQJFMCFSH", kCheatSpielberg, 1 }, // SPIELBERG (Disables all cheats. If number values corresponding to a level and episode number are entered after the cheat word (i.e. "spielberg 1 3" for Phantom Express), you will be spawned to said level and the game will begin recording a demo from your actions.)
+};
+
+bool CCheatMgr::m_bPlayerCheated = false;
+
+bool CCheatMgr::Check(char *pzString)
+{
+ char buffer[80];
+ strcpy(buffer, pzString);
+ Bstrupr(buffer);
+ for (size_t i = 0; i < strlen(pzString); i++)
+ buffer[i]++;
+ for (int i = 0; i < 36; i++)
+ {
+ int nCheatLen = strlen(s_CheatInfo[i].pzString);
+ if (s_CheatInfo[i].flags & 1)
+ {
+ if (!strncmp(buffer, s_CheatInfo[i].pzString, nCheatLen))
+ {
+ Process(s_CheatInfo[i].id, buffer+nCheatLen);
+ return true;
+ }
+ }
+ if (!strcmp(buffer, s_CheatInfo[i].pzString))
+ {
+ Process(s_CheatInfo[i].id, NULL);
+ return true;
+ }
+ }
+ return false;
+}
+
+int parseArgs(char *pzArgs, int *nArg1, int *nArg2)
+{
+ if (!nArg1 || !nArg2)
+ return -1;
+ int nLength = strlen(pzArgs);
+ for (int i = 0; i < nLength; i++)
+ pzArgs[i]--;
+ int stat = sscanf(pzArgs, " %d %d", nArg1, nArg2);
+ if (stat == 2 && (*nArg1 == 0 || *nArg2 == 0))
+ return -1;
+ *nArg1 = ClipRange(*nArg1-1, 0, gEpisodeCount-1);
+ *nArg2 = ClipRange(*nArg2-1, 0, gEpisodeInfo[*nArg1].nLevels-1);
+ return stat;
+}
+
+void CCheatMgr::Process(CCheatMgr::CHEATCODE nCheatCode, char* pzArgs)
+{
+ dassert(nCheatCode > kCheatNone && nCheatCode < kCheatMax);
+
+ if (gDemo.at0) return;
+ if (nCheatCode == kCheatRate)
+ {
+ gShowFps = !gShowFps;
+ return;
+ }
+ if (gGameOptions.nGameType != 0)
+ return;
+ int nEpisode, nLevel;
+ switch (nCheatCode)
+ {
+ case kCheatSpielberg:
+ if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
+ LevelWarpAndRecord(nEpisode, nLevel);
+ break;
+ case kCheat1:
+ SetAmmo(true);
+ break;
+ case kCheatGriswold:
+ SetArmor(true);
+ break;
+ case kCheatSatchel:
+ SetToys(true);
+ break;
+ case kCheatEvaGalli:
+ SetClipMode(!gNoClip);
+ break;
+ case kCheatMpkfa:
+ SetGodMode(!gMe->at31a);
+ break;
+ case kCheatCapInMyAss:
+ SetGodMode(false);
+ break;
+ case kCheatNoCapInMyAss:
+ SetGodMode(true);
+ break;
+ case kCheatIdaho:
+ SetWeapons(true);
+ break;
+ case kCheatKevorkian:
+ actDamageSprite(gMe->at5b, gMe->pSprite, DAMAGE_TYPE_2, 8000);
+ viewSetMessage("Kevorkian approves.");
+ break;
+ case kCheatMcGee:
+ {
+ if (!gMe->pXSprite->burnTime)
+ evPost(gMe->at5b, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(actSpriteIdToOwnerId(gMe->at5b), gMe->pXSprite, 2400);
+ viewSetMessage("You're fired!");
+ break;
+ }
+ case kCheatEdmark:
+ actDamageSprite(gMe->at5b, gMe->pSprite, DAMAGE_TYPE_3, 8000);
+ viewSetMessage("Ahhh...those were the days.");
+ break;
+ case kCheatKrueger:
+ {
+ actHealDude(gMe->pXSprite, 200, 200);
+ gMe->at33e[1] = VanillaMode() ? 200 : 3200;
+ if (!gMe->pXSprite->burnTime)
+ evPost(gMe->at5b, 3, 0, CALLBACK_ID_0);
+ actBurnSprite(actSpriteIdToOwnerId(gMe->at5b), gMe->pXSprite, 2400);
+ viewSetMessage("Flame retardant!");
+ break;
+ }
+ case kCheatSterno:
+ gMe->at36a = 250;
+ break;
+ case kCheat14: // quake (causing a little flicker), not used by any cheat code (dead code)
+ gMe->at35a = 360;
+ break;
+ case kCheatSpork:
+ actHealDude(gMe->pXSprite, 200, 200);
+ break;
+ case kCheatGoonies:
+ SetMap(!gFullMap);
+ break;
+ case kCheatClarice:
+ if (!VanillaMode())
+ {
+ viewSetMessage("You have half armor.");
+ for (int i = 0; i < 3; i++)
+ gMe->at33e[i] = 1600;
+ }
+ break;
+ case kCheatFrankenstein:
+ gMe->packInfo[0].at1 = 100;
+ break;
+ case kCheatCheeseHead:
+ gMe->packInfo[1].at1 = 100;
+ if (!VanillaMode())
+ gMe->at202[18] = gPowerUpInfo[18].at3;
+ break;
+ case kCheatTequila:
+ ToggleWooMode();
+ break;
+ case kCheatFunkyShoes:
+ ToggleBoots();
+ break;
+ case kCheatKeyMaster:
+ SetKeys(true);
+ break;
+ case kCheatOneRing:
+ ToggleInvisibility();
+ break;
+ case kCheatVoorhees:
+ ToggleInvulnerability();
+ break;
+ case kCheatJoJo:
+ ToggleDelirium();
+ break;
+ case kCheatRate: // show FPS, handled before (dead code), leave here for safety
+ return;
+ case kCheatMario:
+ if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
+ LevelWarp(nEpisode, nLevel);
+ break;
+ case kCheatCalgon:
+ if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
+ LevelWarp(nEpisode, nLevel);
+ else
+ if (!VanillaMode())
+ levelEndLevel(0);
+ break;
+ case kCheatLaraCroft:
+ SetInfiniteAmmo(!gInfiniteAmmo);
+ SetWeapons(gInfiniteAmmo);
+ break;
+ case kCheatHongKong:
+ SetWeapons(true);
+ SetInfiniteAmmo(true);
+ break;
+ case kCheatMontana:
+ SetWeapons(true);
+ SetToys(true);
+ break;
+ case kCheatBunz:
+ SetWeapons(true);
+ SetWooMode(true);
+ break;
+ case kCheatCousteau:
+ actHealDude(gMe->pXSprite,200,200);
+ gMe->packInfo[1].at1 = 100;
+ if (!VanillaMode())
+ gMe->at202[18] = gPowerUpInfo[18].at3;
+ break;
+ case kCheatForkYou:
+ SetInfiniteAmmo(false);
+ SetMap(false);
+ SetWeapons(false);
+ SetAmmo(false);
+ SetArmor(false);
+ SetToys(false);
+ SetKeys(false);
+ SetWooMode(true);
+ powerupActivate(gMe, 28);
+ gMe->pXSprite->health = 16;
+ gMe->atcb[1] = 1;
+ gMe->atbd = 0;
+ gMe->atbe = 1;
+ break;
+ default:
+ break;
+ }
+ m_bPlayerCheated = true;
+}
+
+void CCheatMgr::sub_5BCF4(void)
+{
+ m_bPlayerCheated = 0;
+ playerSetGodMode(gMe, 0);
+ gNoClip = 0;
+ packClear(gMe);
+ gInfiniteAmmo = 0;
+ gFullMap = 0;
+}
+
+class MessagesLoadSave : public LoadSave
+{
+public:
+ virtual void Load();
+ virtual void Save();
+};
+
+void MessagesLoadSave::Load()
+{
+ Read(&CCheatMgr::m_bPlayerCheated, sizeof(CCheatMgr::m_bPlayerCheated));
+}
+
+void MessagesLoadSave::Save()
+{
+ Write(&CCheatMgr::m_bPlayerCheated, sizeof(CCheatMgr::m_bPlayerCheated));
+}
+
+static MessagesLoadSave *myLoadSave;
+
+void MessagesLoadSaveConstruct(void)
+{
+ myLoadSave = new MessagesLoadSave();
+}
diff --git a/source/blood/src/messages.h b/source/blood/src/messages.h
new file mode 100644
index 000000000..225c1d2ac
--- /dev/null
+++ b/source/blood/src/messages.h
@@ -0,0 +1,146 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "player.h"
+
+class CGameMessageMgr
+{
+public:
+ struct messageStruct
+ {
+ int at0;
+ char at4[81];
+ };
+ char at0;
+ int at1;
+ int at5;
+ int at9;
+ int atd;
+ int at11;
+ int at15;
+ int at19;
+ int at1d;
+ char at21;
+ int at22;
+ int at26;
+ int at2a;
+ messageStruct at2e[16];
+ CGameMessageMgr();
+ void SetState(char state);
+ void Add(const char *, char);
+ void Display(void);
+ void Clear();
+ void SetMaxMessages(int nMessages);
+ void SetFont(int nFont);
+ void SetCoordinates(int x, int y);
+ void SetMessageTime(int nTime);
+ void SetMessageFlags(unsigned int nFlags);
+};
+
+
+class CPlayerMsg
+{
+public:
+ int at0;
+ char at4[41];
+ CPlayerMsg() { at0 = 0; at4[0] = 0; }
+ void Clear(void);
+ void Term(void);
+ void Draw(void);
+ bool AddChar(char);
+ void DelChar(void);
+ void Set(const char *pzString);
+ void Send(void);
+ void ProcessKeys(void);
+};
+
+class CCheatMgr
+{
+public:
+ static bool m_bPlayerCheated;
+ enum CHEATCODE
+ {
+ kCheatNone = 0,
+ kCheat1, // refills ammo, no cheat code for it
+ kCheatGriswold,
+ kCheatSatchel,
+ kCheatEvaGalli,
+ kCheatMpkfa,
+ kCheatCapInMyAss,
+ kCheatNoCapInMyAss,
+ kCheatIdaho,
+ kCheatKevorkian,
+ kCheatMcGee,
+ kCheatEdmark,
+ kCheatKrueger,
+ kCheatSterno,
+ kCheat14, // quake effect, not used
+ kCheatSpork,
+ kCheatGoonies,
+ kCheatClarice,
+ kCheatFrankenstein,
+ kCheatCheeseHead,
+ kCheatTequila,
+ kCheatFunkyShoes,
+ kCheatKeyMaster,
+ kCheatOneRing,
+ kCheatVoorhees,
+ kCheatJoJo,
+ kCheatGateKeeper,
+ kCheatRate,
+ kCheatMario,
+ kCheatLaraCroft,
+ kCheatHongKong,
+ kCheatMontana,
+ kCheatBunz,
+ kCheatCousteau,
+ kCheatForkYou,
+ kCheatLieberMan,
+ kCheatSpielberg,
+ kCheatCalgon,
+ kCheatMax
+ };
+ struct CHEATINFO
+ {
+ const char* pzString;
+ CHEATCODE id;
+ int flags;
+ };
+ static CHEATINFO s_CheatInfo[];
+ CCheatMgr() {}
+ bool Check(char *pzString);
+ void Process(CHEATCODE nCheatCode, char* pzArgs);
+ void sub_5BCF4(void);
+};
+
+extern CPlayerMsg gPlayerMsg;
+extern CCheatMgr gCheatMgr;
+
+void SetAmmo(bool stat);
+void SetWeapons(bool stat);
+void SetToys(bool stat);
+void SetArmor(bool stat);
+void SetKeys(bool stat);
+void SetGodMode(bool god);
+void SetClipMode(bool noclip);
diff --git a/source/blood/src/midi.cpp b/source/blood/src/midi.cpp
new file mode 100644
index 000000000..a4531928f
--- /dev/null
+++ b/source/blood/src/midi.cpp
@@ -0,0 +1,958 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+/**********************************************************************
+ module: MIDI.C
+
+ author: James R. Dose
+ date: May 25, 1994
+
+ Midi song file playback routines.
+
+ (c) Copyright 1994 James R. Dose. All Rights Reserved.
+**********************************************************************/
+
+// This object is shared by all Build games with MIDI playback!
+
+#include "compat.h"
+#include "music.h"
+#include "_midi.h"
+#include "midi.h"
+#include "mpu401.h"
+#include "compat.h"
+#include "pragmas.h"
+
+#include "multivoc.h"
+
+#include "windows_inc.h"
+
+extern int32_t MUSIC_SoundDevice;
+
+static const int32_t _MIDI_CommandLengths[ NUM_MIDI_CHANNELS ] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0
+};
+
+static track *_MIDI_TrackPtr = NULL;
+static int32_t _MIDI_TrackMemSize;
+static int32_t _MIDI_NumTracks;
+
+static int32_t _MIDI_SongActive = FALSE;
+static int32_t _MIDI_SongLoaded = FALSE;
+static int32_t _MIDI_Loop = FALSE;
+
+static int32_t _MIDI_Division;
+static int32_t _MIDI_Tick = 0;
+static int32_t _MIDI_Beat = 1;
+static int32_t _MIDI_Measure = 1;
+static uint32_t _MIDI_Time;
+static int32_t _MIDI_BeatsPerMeasure;
+static int32_t _MIDI_TicksPerBeat;
+static int32_t _MIDI_TimeBase;
+static int32_t _MIDI_FPSecondsPerTick;
+static uint32_t _MIDI_TotalTime;
+static int32_t _MIDI_TotalTicks;
+static int32_t _MIDI_TotalBeats;
+static int32_t _MIDI_TotalMeasures;
+
+uint32_t _MIDI_PositionInTicks;
+uint32_t _MIDI_GlobalPositionInTicks;
+
+static int32_t _MIDI_Context;
+
+static int32_t _MIDI_ActiveTracks;
+static int32_t _MIDI_TotalVolume = MIDI_MaxVolume;
+
+static int32_t _MIDI_ChannelVolume[ NUM_MIDI_CHANNELS ];
+
+static midifuncs *_MIDI_Funcs = NULL;
+
+static int32_t Reset = FALSE;
+
+int32_t MIDI_Tempo = 120;
+
+static int32_t _MIDI_PlayRoutine = -1;
+static int32_t _MIDI_MixRate = 44100;
+static int32_t _MIDI_MixTimer = 0;
+
+static int32_t _MIDI_ReadNumber(void *from, size_t size)
+{
+ if (size > 4)
+ size = 4;
+
+ char *FromPtr = (char *)from;
+ int32_t value = 0;
+
+ while (size--)
+ {
+ value <<= 8;
+ value += *FromPtr++;
+ }
+
+ return value;
+}
+
+static int32_t _MIDI_ReadDelta(track *ptr)
+{
+ int32_t value;
+
+ GET_NEXT_EVENT(ptr, value);
+
+ if (value & 0x80)
+ {
+ value &= 0x7f;
+ char c;
+
+ do
+ {
+ GET_NEXT_EVENT(ptr, c);
+ value = (value << 7) + (c & 0x7f);
+ }
+ while (c & 0x80);
+ }
+
+ return value;
+}
+
+static void _MIDI_ResetTracks(void)
+{
+ _MIDI_Tick = 0;
+ _MIDI_Beat = 1;
+ _MIDI_Measure = 1;
+ _MIDI_Time = 0;
+ _MIDI_BeatsPerMeasure = 4;
+ _MIDI_TicksPerBeat = _MIDI_Division;
+ _MIDI_TimeBase = 4;
+ _MIDI_PositionInTicks = 0;
+ _MIDI_ActiveTracks = 0;
+ _MIDI_Context = 0;
+
+ track *ptr = _MIDI_TrackPtr;
+ for (bssize_t i = 0; i < _MIDI_NumTracks; ++i)
+ {
+ ptr->pos = ptr->start;
+ ptr->delay = _MIDI_ReadDelta(ptr);
+ ptr->active = ptr->EMIDI_IncludeTrack;
+ ptr->RunningStatus = 0;
+ ptr->currentcontext = 0;
+ ptr->context[ 0 ].loopstart = ptr->start;
+ ptr->context[ 0 ].loopcount = 0;
+
+ if (ptr->active)
+ _MIDI_ActiveTracks++;
+
+ ptr++;
+ }
+}
+
+static void _MIDI_AdvanceTick(void)
+{
+ _MIDI_PositionInTicks++;
+ _MIDI_Time += _MIDI_FPSecondsPerTick;
+
+ _MIDI_Tick++;
+ while (_MIDI_Tick > _MIDI_TicksPerBeat)
+ {
+ _MIDI_Tick -= _MIDI_TicksPerBeat;
+ _MIDI_Beat++;
+ }
+ while (_MIDI_Beat > _MIDI_BeatsPerMeasure)
+ {
+ _MIDI_Beat -= _MIDI_BeatsPerMeasure;
+ _MIDI_Measure++;
+ }
+}
+
+static void _MIDI_SysEx(track *Track)
+{
+ int32_t length = _MIDI_ReadDelta(Track);
+ Track->pos += length;
+}
+
+
+static void _MIDI_MetaEvent(track *Track)
+{
+ int32_t command;
+ int32_t length;
+
+ GET_NEXT_EVENT(Track, command);
+ GET_NEXT_EVENT(Track, length);
+
+ switch (command)
+ {
+ case MIDI_END_OF_TRACK:
+ Track->active = FALSE;
+
+ _MIDI_ActiveTracks--;
+ break;
+
+ case MIDI_TEMPO_CHANGE:
+ {
+ int32_t tempo = tabledivide32_noinline(60000000L, _MIDI_ReadNumber(Track->pos, 3));
+ MIDI_SetTempo(tempo);
+ break;
+ }
+
+ case MIDI_TIME_SIGNATURE:
+ {
+ if ((_MIDI_Tick > 0) || (_MIDI_Beat > 1))
+ _MIDI_Measure++;
+
+ _MIDI_Tick = 0;
+ _MIDI_Beat = 1;
+ _MIDI_TimeBase = 1;
+ _MIDI_BeatsPerMeasure = (int32_t)*Track->pos;
+ int32_t denominator = (int32_t) * (Track->pos + 1);
+
+ while (denominator > 0)
+ {
+ _MIDI_TimeBase += _MIDI_TimeBase;
+ denominator--;
+ }
+
+ _MIDI_TicksPerBeat = tabledivide32_noinline(_MIDI_Division * 4, _MIDI_TimeBase);
+ break;
+ }
+ }
+
+ Track->pos += length;
+}
+
+static int32_t _MIDI_InterpretControllerInfo(track *Track, int32_t TimeSet, int32_t channel, int32_t c1, int32_t c2)
+{
+ track *trackptr;
+ int32_t tracknum;
+ int32_t loopcount;
+
+ switch (c1)
+ {
+ case MIDI_MONO_MODE_ON :
+ Track->pos++;
+ break;
+
+ case MIDI_VOLUME :
+ if (!Track->EMIDI_VolumeChange)
+ _MIDI_SetChannelVolume(channel, c2);
+ break;
+
+ case EMIDI_INCLUDE_TRACK :
+ case EMIDI_EXCLUDE_TRACK :
+ break;
+
+ case EMIDI_PROGRAM_CHANGE :
+ if (Track->EMIDI_ProgramChange)
+ _MIDI_Funcs->ProgramChange(channel, c2 & 0x7f);
+ break;
+
+ case EMIDI_VOLUME_CHANGE :
+ if (Track->EMIDI_VolumeChange)
+ _MIDI_SetChannelVolume(channel, c2);
+ break;
+
+ case EMIDI_CONTEXT_START :
+ break;
+
+ case EMIDI_CONTEXT_END :
+ if ((Track->currentcontext == _MIDI_Context) || (_MIDI_Context < 0) ||
+ (Track->context[_MIDI_Context].pos == NULL))
+ break;
+
+ Track->currentcontext = _MIDI_Context;
+ Track->context[ 0 ].loopstart = Track->context[ _MIDI_Context ].loopstart;
+ Track->context[ 0 ].loopcount = Track->context[ _MIDI_Context ].loopcount;
+ Track->pos = Track->context[ _MIDI_Context ].pos;
+ Track->RunningStatus = Track->context[ _MIDI_Context ].RunningStatus;
+
+ if (TimeSet)
+ {
+ break;
+ }
+
+ _MIDI_Time = Track->context[ _MIDI_Context ].time;
+ _MIDI_FPSecondsPerTick = Track->context[ _MIDI_Context ].FPSecondsPerTick;
+ _MIDI_Tick = Track->context[ _MIDI_Context ].tick;
+ _MIDI_Beat = Track->context[ _MIDI_Context ].beat;
+ _MIDI_Measure = Track->context[ _MIDI_Context ].measure;
+ _MIDI_BeatsPerMeasure = Track->context[ _MIDI_Context ].BeatsPerMeasure;
+ _MIDI_TicksPerBeat = Track->context[ _MIDI_Context ].TicksPerBeat;
+ _MIDI_TimeBase = Track->context[ _MIDI_Context ].TimeBase;
+ TimeSet = TRUE;
+ break;
+
+ case EMIDI_LOOP_START :
+ case EMIDI_SONG_LOOP_START :
+ loopcount = (c2 == 0) ? EMIDI_INFINITE : c2;
+
+ if (c1 == EMIDI_SONG_LOOP_START)
+ {
+ trackptr = _MIDI_TrackPtr;
+ tracknum = _MIDI_NumTracks;
+ }
+ else
+ {
+ trackptr = Track;
+ tracknum = 1;
+ }
+
+ while (tracknum > 0)
+ {
+ trackptr->context[ 0 ].loopcount = loopcount;
+ trackptr->context[ 0 ].pos = trackptr->pos;
+ trackptr->context[ 0 ].loopstart = trackptr->pos;
+ trackptr->context[ 0 ].RunningStatus = trackptr->RunningStatus;
+ trackptr->context[ 0 ].active = trackptr->active;
+ trackptr->context[ 0 ].delay = trackptr->delay;
+ trackptr->context[ 0 ].time = _MIDI_Time;
+ trackptr->context[ 0 ].FPSecondsPerTick = _MIDI_FPSecondsPerTick;
+ trackptr->context[ 0 ].tick = _MIDI_Tick;
+ trackptr->context[ 0 ].beat = _MIDI_Beat;
+ trackptr->context[ 0 ].measure = _MIDI_Measure;
+ trackptr->context[ 0 ].BeatsPerMeasure = _MIDI_BeatsPerMeasure;
+ trackptr->context[ 0 ].TicksPerBeat = _MIDI_TicksPerBeat;
+ trackptr->context[ 0 ].TimeBase = _MIDI_TimeBase;
+ trackptr++;
+ tracknum--;
+ }
+ break;
+
+ case EMIDI_LOOP_END :
+ case EMIDI_SONG_LOOP_END :
+ if ((c2 != EMIDI_END_LOOP_VALUE) || (Track->context[0].loopstart == NULL) || (Track->context[0].loopcount == 0))
+ break;
+
+ if (c1 == EMIDI_SONG_LOOP_END)
+ {
+ trackptr = _MIDI_TrackPtr;
+ tracknum = _MIDI_NumTracks;
+ _MIDI_ActiveTracks = 0;
+ }
+ else
+ {
+ trackptr = Track;
+ tracknum = 1;
+ _MIDI_ActiveTracks--;
+ }
+
+ while (tracknum > 0)
+ {
+ if (trackptr->context[ 0 ].loopcount != EMIDI_INFINITE)
+ {
+ trackptr->context[ 0 ].loopcount--;
+ }
+
+ trackptr->pos = trackptr->context[ 0 ].loopstart;
+ trackptr->RunningStatus = trackptr->context[ 0 ].RunningStatus;
+ trackptr->delay = trackptr->context[ 0 ].delay;
+ trackptr->active = trackptr->context[ 0 ].active;
+ if (trackptr->active)
+ {
+ _MIDI_ActiveTracks++;
+ }
+
+ if (!TimeSet)
+ {
+ _MIDI_Time = trackptr->context[ 0 ].time;
+ _MIDI_FPSecondsPerTick = trackptr->context[ 0 ].FPSecondsPerTick;
+ _MIDI_Tick = trackptr->context[ 0 ].tick;
+ _MIDI_Beat = trackptr->context[ 0 ].beat;
+ _MIDI_Measure = trackptr->context[ 0 ].measure;
+ _MIDI_BeatsPerMeasure = trackptr->context[ 0 ].BeatsPerMeasure;
+ _MIDI_TicksPerBeat = trackptr->context[ 0 ].TicksPerBeat;
+ _MIDI_TimeBase = trackptr->context[ 0 ].TimeBase;
+ TimeSet = TRUE;
+ }
+
+ trackptr++;
+ tracknum--;
+ }
+ break;
+
+ default :
+ if (_MIDI_Funcs->ControlChange)
+ _MIDI_Funcs->ControlChange(channel, c1, c2);
+ }
+
+ return TimeSet;
+}
+
+static void _MIDI_ServiceRoutine(void)
+{
+ if (!_MIDI_SongActive)
+ return;
+
+ track *Track = _MIDI_TrackPtr;
+ int32_t tracknum = 0;
+ int32_t TimeSet = FALSE;
+ int32_t c1 = 0;
+ int32_t c2 = 0;
+
+ while (tracknum < _MIDI_NumTracks)
+ {
+ while ((Track->active) && (Track->delay == 0))
+ {
+ int32_t event;
+ GET_NEXT_EVENT(Track, event);
+
+ if (GET_MIDI_COMMAND(event) == MIDI_SPECIAL)
+ {
+ switch (event)
+ {
+ case MIDI_SYSEX:
+ case MIDI_SYSEX_CONTINUE: _MIDI_SysEx(Track); break;
+ case MIDI_META_EVENT: _MIDI_MetaEvent(Track); break;
+ }
+
+ if (Track->active)
+ Track->delay = _MIDI_ReadDelta(Track);
+ continue;
+ }
+
+ if (event & MIDI_RUNNING_STATUS)
+ Track->RunningStatus = event;
+ else
+ {
+ event = Track->RunningStatus;
+ Track->pos--;
+ }
+
+ int const channel = GET_MIDI_CHANNEL(event);
+ int const command = GET_MIDI_COMMAND(event);
+
+ if (_MIDI_CommandLengths[ command ] > 0)
+ {
+ GET_NEXT_EVENT(Track, c1);
+ if (_MIDI_CommandLengths[ command ] > 1)
+ GET_NEXT_EVENT(Track, c2);
+ }
+
+ switch (command)
+ {
+ case MIDI_NOTE_OFF:
+ if (_MIDI_Funcs->NoteOff)
+ _MIDI_Funcs->NoteOff(channel, c1, c2);
+ break;
+
+ case MIDI_NOTE_ON:
+ if (_MIDI_Funcs->NoteOn)
+ _MIDI_Funcs->NoteOn(channel, c1, c2);
+ break;
+
+ case MIDI_POLY_AFTER_TCH:
+ if (_MIDI_Funcs->PolyAftertouch)
+ _MIDI_Funcs->PolyAftertouch(channel, c1, c2);
+ break;
+
+ case MIDI_CONTROL_CHANGE:
+ TimeSet = _MIDI_InterpretControllerInfo(Track, TimeSet, channel, c1, c2);
+ break;
+
+ case MIDI_PROGRAM_CHANGE:
+ if ((_MIDI_Funcs->ProgramChange) && (!Track->EMIDI_ProgramChange))
+ _MIDI_Funcs->ProgramChange(channel, c1 & 0x7f);
+ break;
+
+ case MIDI_AFTER_TOUCH:
+ if (_MIDI_Funcs->ChannelAftertouch)
+ _MIDI_Funcs->ChannelAftertouch(channel, c1);
+ break;
+
+ case MIDI_PITCH_BEND:
+ if (_MIDI_Funcs->PitchBend)
+ _MIDI_Funcs->PitchBend(channel, c1, c2);
+ break;
+
+ default: break;
+ }
+
+ Track->delay = _MIDI_ReadDelta(Track);
+ }
+
+ Track->delay--;
+ Track++;
+ tracknum++;
+
+ if (_MIDI_ActiveTracks == 0)
+ {
+ _MIDI_ResetTracks();
+ if (_MIDI_Loop)
+ {
+ tracknum = 0;
+ Track = _MIDI_TrackPtr;
+ }
+ else
+ {
+ _MIDI_SongActive = FALSE;
+ break;
+ }
+ }
+ }
+
+ _MIDI_AdvanceTick();
+ _MIDI_GlobalPositionInTicks++;
+}
+
+static int32_t _MIDI_SendControlChange(int32_t channel, int32_t c1, int32_t c2)
+{
+ if (_MIDI_Funcs == NULL || _MIDI_Funcs->ControlChange == NULL)
+ return MIDI_Error;
+
+ _MIDI_Funcs->ControlChange(channel, c1, c2);
+
+ return MIDI_Ok;
+}
+
+int32_t MIDI_AllNotesOff(void)
+{
+ for (bssize_t channel = 0; channel < NUM_MIDI_CHANNELS; channel++)
+ {
+ _MIDI_SendControlChange(channel, 0x40, 0);
+ _MIDI_SendControlChange(channel, MIDI_ALL_NOTES_OFF, 0);
+ _MIDI_SendControlChange(channel, 0x78, 0);
+ }
+
+ return MIDI_Ok;
+}
+
+static void _MIDI_SetChannelVolume(int32_t channel, int32_t volume)
+{
+ _MIDI_ChannelVolume[ channel ] = volume;
+
+ if (_MIDI_Funcs == NULL || _MIDI_Funcs->ControlChange == NULL)
+ return;
+
+ volume *= _MIDI_TotalVolume;
+ volume = tabledivide32_noinline(volume, MIDI_MaxVolume);
+
+ _MIDI_Funcs->ControlChange(channel, MIDI_VOLUME, volume);
+}
+
+static void _MIDI_SendChannelVolumes(void)
+{
+ for (bssize_t channel = 0; channel < NUM_MIDI_CHANNELS; channel++)
+ _MIDI_SetChannelVolume(channel, _MIDI_ChannelVolume[channel]);
+}
+
+int32_t MIDI_Reset(void)
+{
+ MIDI_AllNotesOff();
+
+ for (bssize_t channel = 0; channel < NUM_MIDI_CHANNELS; channel++)
+ {
+ _MIDI_SendControlChange(channel, MIDI_RESET_ALL_CONTROLLERS, 0);
+ _MIDI_SendControlChange(channel, MIDI_RPN_MSB, MIDI_PITCHBEND_MSB);
+ _MIDI_SendControlChange(channel, MIDI_RPN_LSB, MIDI_PITCHBEND_LSB);
+ _MIDI_SendControlChange(channel, MIDI_DATAENTRY_MSB, 2); /* Pitch Bend Sensitivity MSB */
+ _MIDI_SendControlChange(channel, MIDI_DATAENTRY_LSB, 0); /* Pitch Bend Sensitivity LSB */
+ _MIDI_ChannelVolume[ channel ] = GENMIDI_DefaultVolume;
+ }
+
+ _MIDI_SendChannelVolumes();
+
+ Reset = TRUE;
+
+ return MIDI_Ok;
+}
+
+int32_t MIDI_SetVolume(int32_t volume)
+{
+ if (_MIDI_Funcs == NULL)
+ return MIDI_NullMidiModule;
+
+ _MIDI_TotalVolume = max(0, min(MIDI_MaxVolume, volume));
+ _MIDI_SendChannelVolumes();
+
+ return MIDI_Ok;
+}
+
+int32_t MIDI_GetVolume(void) { return (_MIDI_Funcs == NULL) ? MIDI_NullMidiModule : _MIDI_TotalVolume; }
+
+void MIDI_SetLoopFlag(int32_t loopflag) { _MIDI_Loop = loopflag; }
+
+void MIDI_ContinueSong(void)
+{
+ if (!_MIDI_SongLoaded)
+ return;
+
+ _MIDI_SongActive = TRUE;
+ MPU_Unpause();
+}
+
+void MIDI_PauseSong(void)
+{
+ if (!_MIDI_SongLoaded)
+ return;
+
+ _MIDI_SongActive = FALSE;
+ MIDI_AllNotesOff();
+ MPU_Pause();
+}
+
+void MIDI_SetMidiFuncs(midifuncs *funcs) { _MIDI_Funcs = funcs; }
+
+void MIDI_StopSong(void)
+{
+ if (!_MIDI_SongLoaded)
+ return;
+
+ _MIDI_SongActive = FALSE;
+ _MIDI_SongLoaded = FALSE;
+
+ MPU_Reset();
+ MPU_Init(MUSIC_SoundDevice);
+
+ MIDI_Reset();
+ _MIDI_ResetTracks();
+
+ DO_FREE_AND_NULL(_MIDI_TrackPtr);
+
+ _MIDI_NumTracks = 0;
+ _MIDI_TrackMemSize = 0;
+
+ _MIDI_TotalTime = 0;
+ _MIDI_TotalTicks = 0;
+ _MIDI_TotalBeats = 0;
+ _MIDI_TotalMeasures = 0;
+
+}
+
+int32_t MIDI_PlaySong(char *song, int32_t loopflag)
+{
+ extern int32_t MV_MixRate;
+ int32_t numtracks;
+ int32_t format;
+ int32_t headersize;
+ int32_t tracklength;
+ track *CurrentTrack;
+ char *ptr;
+
+ if (_MIDI_Funcs == NULL)
+ return MIDI_NullMidiModule;
+
+ if (B_UNBUF32(song) != MIDI_HEADER_SIGNATURE)
+ return MIDI_InvalidMidiFile;
+
+ song += 4;
+ headersize = _MIDI_ReadNumber(song, 4);
+ song += 4;
+ format = _MIDI_ReadNumber(song, 2);
+ int32_t My_MIDI_NumTracks = _MIDI_ReadNumber(song + 2, 2);
+ int32_t My_MIDI_Division = _MIDI_ReadNumber(song + 4, 2);
+ if (My_MIDI_Division < 0)
+ {
+ // If a SMPTE time division is given, just set to 96 so no errors occur
+ My_MIDI_Division = 96;
+ }
+
+ if (format > MAX_FORMAT)
+ return MIDI_UnknownMidiFormat;
+
+ ptr = song + headersize;
+
+ if (My_MIDI_NumTracks == 0)
+ return MIDI_NoTracks;
+
+ int32_t My_MIDI_TrackMemSize = My_MIDI_NumTracks * sizeof(track);
+ track * My_MIDI_TrackPtr = (track *)Xmalloc(My_MIDI_TrackMemSize);
+
+ CurrentTrack = My_MIDI_TrackPtr;
+ numtracks = My_MIDI_NumTracks;
+
+ while (numtracks--)
+ {
+ if (B_UNBUF32(ptr) != MIDI_TRACK_SIGNATURE)
+ {
+ DO_FREE_AND_NULL(My_MIDI_TrackPtr);
+
+ My_MIDI_TrackMemSize = 0;
+
+ return MIDI_InvalidTrack;
+ }
+
+ tracklength = _MIDI_ReadNumber(ptr + 4, 4);
+ ptr += 8;
+ CurrentTrack->start = ptr;
+ ptr += tracklength;
+ CurrentTrack++;
+ }
+
+ // at this point we know song load is successful
+
+ if (_MIDI_SongLoaded)
+ MIDI_StopSong();
+
+ MPU_Init(0/*MUSIC_SoundDevice*/);
+
+ _MIDI_Loop = loopflag;
+ _MIDI_NumTracks = My_MIDI_NumTracks;
+ _MIDI_Division = My_MIDI_Division;
+ _MIDI_TrackMemSize = My_MIDI_TrackMemSize;
+ _MIDI_TrackPtr = My_MIDI_TrackPtr;
+
+ _MIDI_InitEMIDI();
+ _MIDI_ResetTracks();
+
+ if (!Reset)
+ MIDI_Reset();
+
+ Reset = FALSE;
+
+ MIDI_SetDivision(_MIDI_Division);
+
+ _MIDI_SongLoaded = TRUE;
+ _MIDI_SongActive = TRUE;
+
+ while (_MPU_BuffersWaiting < 4) _MIDI_ServiceRoutine();
+ MPU_BeginPlayback();
+
+ return MIDI_Ok;
+}
+
+void MIDI_SetTempo(int32_t tempo)
+{
+ int32_t tickspersecond;
+
+ MIDI_Tempo = tempo;
+ tickspersecond = ((tempo) * _MIDI_Division)/60;
+ _MIDI_FPSecondsPerTick = tabledivide32_noinline(1 << TIME_PRECISION, tickspersecond);
+ MPU_SetTempo(tempo);
+}
+
+void MIDI_SetDivision(int32_t division)
+{
+ MPU_SetDivision(division);
+}
+
+int32_t MIDI_GetTempo(void) { return MIDI_Tempo; }
+
+static void _MIDI_InitEMIDI(void)
+{
+ int32_t type = EMIDI_GeneralMIDI;
+
+ _MIDI_ResetTracks();
+
+ _MIDI_TotalTime = 0;
+ _MIDI_TotalTicks = 0;
+ _MIDI_TotalBeats = 0;
+ _MIDI_TotalMeasures = 0;
+
+ track *Track = _MIDI_TrackPtr;
+ int32_t tracknum = 0;
+
+ while ((tracknum < _MIDI_NumTracks) && (Track != NULL))
+ {
+ _MIDI_Tick = 0;
+ _MIDI_Beat = 1;
+ _MIDI_Measure = 1;
+ _MIDI_Time = 0;
+ _MIDI_BeatsPerMeasure = 4;
+ _MIDI_TicksPerBeat = _MIDI_Division;
+ _MIDI_TimeBase = 4;
+
+ _MIDI_PositionInTicks = 0;
+ _MIDI_ActiveTracks = 0;
+ _MIDI_Context = -1;
+
+ Track->RunningStatus = 0;
+ Track->active = TRUE;
+
+ Track->EMIDI_ProgramChange = FALSE;
+ Track->EMIDI_VolumeChange = FALSE;
+ Track->EMIDI_IncludeTrack = TRUE;
+
+ memset(Track->context, 0, sizeof(Track->context));
+
+ while (Track->delay > 0)
+ {
+ _MIDI_AdvanceTick();
+ Track->delay--;
+ }
+
+ int32_t IncludeFound = FALSE;
+
+ while (Track->active)
+ {
+ int32_t event;
+
+ GET_NEXT_EVENT(Track, event);
+
+ if (GET_MIDI_COMMAND(event) == MIDI_SPECIAL)
+ {
+ switch (event)
+ {
+ case MIDI_SYSEX:
+ case MIDI_SYSEX_CONTINUE: _MIDI_SysEx(Track); break;
+ case MIDI_META_EVENT: _MIDI_MetaEvent(Track); break;
+ }
+
+ if (Track->active)
+ {
+ Track->delay = _MIDI_ReadDelta(Track);
+ while (Track->delay > 0)
+ {
+ _MIDI_AdvanceTick();
+ Track->delay--;
+ }
+ }
+
+ continue;
+ }
+
+ if (event & MIDI_RUNNING_STATUS)
+ Track->RunningStatus = event;
+ else
+ {
+ event = Track->RunningStatus;
+ Track->pos--;
+ }
+
+// channel = GET_MIDI_CHANNEL(event);
+ int const command = GET_MIDI_COMMAND(event);
+ int length = _MIDI_CommandLengths[ command ];
+
+ if (command == MIDI_CONTROL_CHANGE)
+ {
+ if (*Track->pos == MIDI_MONO_MODE_ON)
+ length++;
+
+ int32_t c1, c2;
+ GET_NEXT_EVENT(Track, c1);
+ GET_NEXT_EVENT(Track, c2);
+ length -= 2;
+
+ switch (c1)
+ {
+ case EMIDI_LOOP_START :
+ case EMIDI_SONG_LOOP_START :
+ Track->context[ 0 ].loopcount = (c2 == 0) ? EMIDI_INFINITE : c2;
+ Track->context[ 0 ].pos = Track->pos;
+ Track->context[ 0 ].loopstart = Track->pos;
+ Track->context[ 0 ].RunningStatus = Track->RunningStatus;
+ Track->context[ 0 ].time = _MIDI_Time;
+ Track->context[ 0 ].FPSecondsPerTick = _MIDI_FPSecondsPerTick;
+ Track->context[ 0 ].tick = _MIDI_Tick;
+ Track->context[ 0 ].beat = _MIDI_Beat;
+ Track->context[ 0 ].measure = _MIDI_Measure;
+ Track->context[ 0 ].BeatsPerMeasure = _MIDI_BeatsPerMeasure;
+ Track->context[ 0 ].TicksPerBeat = _MIDI_TicksPerBeat;
+ Track->context[ 0 ].TimeBase = _MIDI_TimeBase;
+ break;
+
+ case EMIDI_LOOP_END :
+ case EMIDI_SONG_LOOP_END :
+ if (c2 == EMIDI_END_LOOP_VALUE)
+ {
+ Track->context[ 0 ].loopstart = NULL;
+ Track->context[ 0 ].loopcount = 0;
+ }
+ break;
+
+ case EMIDI_INCLUDE_TRACK :
+ if (EMIDI_AffectsCurrentCard(c2, type))
+ {
+ //printf( "Include track %d on card %d\n", tracknum, c2 );
+ IncludeFound = TRUE;
+ Track->EMIDI_IncludeTrack = TRUE;
+ }
+ else if (!IncludeFound)
+ {
+ //printf( "Track excluded %d on card %d\n", tracknum, c2 );
+ IncludeFound = TRUE;
+ Track->EMIDI_IncludeTrack = FALSE;
+ }
+ break;
+
+ case EMIDI_EXCLUDE_TRACK :
+ if (EMIDI_AffectsCurrentCard(c2, type))
+ {
+ //printf( "Exclude track %d on card %d\n", tracknum, c2 );
+ Track->EMIDI_IncludeTrack = FALSE;
+ }
+ break;
+
+ case EMIDI_PROGRAM_CHANGE :
+ if (!Track->EMIDI_ProgramChange)
+ //printf( "Program change on track %d\n", tracknum );
+ Track->EMIDI_ProgramChange = TRUE;
+ break;
+
+ case EMIDI_VOLUME_CHANGE :
+ if (!Track->EMIDI_VolumeChange)
+ //printf( "Volume change on track %d\n", tracknum );
+ Track->EMIDI_VolumeChange = TRUE;
+ break;
+
+ case EMIDI_CONTEXT_START :
+ if ((c2 > 0) && (c2 < EMIDI_NUM_CONTEXTS))
+ {
+ Track->context[ c2 ].pos = Track->pos;
+ Track->context[ c2 ].loopstart = Track->context[ 0 ].loopstart;
+ Track->context[ c2 ].loopcount = Track->context[ 0 ].loopcount;
+ Track->context[ c2 ].RunningStatus = Track->RunningStatus;
+ Track->context[ c2 ].time = _MIDI_Time;
+ Track->context[ c2 ].FPSecondsPerTick = _MIDI_FPSecondsPerTick;
+ Track->context[ c2 ].tick = _MIDI_Tick;
+ Track->context[ c2 ].beat = _MIDI_Beat;
+ Track->context[ c2 ].measure = _MIDI_Measure;
+ Track->context[ c2 ].BeatsPerMeasure = _MIDI_BeatsPerMeasure;
+ Track->context[ c2 ].TicksPerBeat = _MIDI_TicksPerBeat;
+ Track->context[ c2 ].TimeBase = _MIDI_TimeBase;
+ }
+ break;
+
+ case EMIDI_CONTEXT_END :
+ break;
+ }
+ }
+
+ Track->pos += length;
+ Track->delay = _MIDI_ReadDelta(Track);
+
+ while (Track->delay > 0)
+ {
+ _MIDI_AdvanceTick();
+ Track->delay--;
+ }
+ }
+
+ _MIDI_TotalTime = max(_MIDI_TotalTime, _MIDI_Time);
+ if (RELATIVE_BEAT(_MIDI_Measure, _MIDI_Beat, _MIDI_Tick) >
+ RELATIVE_BEAT(_MIDI_TotalMeasures, _MIDI_TotalBeats, _MIDI_TotalTicks))
+ {
+ _MIDI_TotalTicks = _MIDI_Tick;
+ _MIDI_TotalBeats = _MIDI_Beat;
+ _MIDI_TotalMeasures = _MIDI_Measure;
+ }
+
+ Track++;
+ tracknum++;
+ }
+
+ _MIDI_ResetTracks();
+}
+
+void MIDI_UpdateMusic(void)
+{
+ if (!_MIDI_SongLoaded || !_MIDI_SongActive) return;
+ while (_MPU_BuffersWaiting < 4) _MIDI_ServiceRoutine();
+}
+
diff --git a/source/blood/src/midi.h b/source/blood/src/midi.h
new file mode 100644
index 000000000..86f1ecbdd
--- /dev/null
+++ b/source/blood/src/midi.h
@@ -0,0 +1,89 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#ifndef __MIDI_H
+#define __MIDI_H
+
+enum MIDI_Errors
+ {
+ MIDI_Warning = -2,
+ MIDI_Error = -1,
+ MIDI_Ok = 0,
+ MIDI_NullMidiModule,
+ MIDI_InvalidMidiFile,
+ MIDI_UnknownMidiFormat,
+ MIDI_NoTracks,
+ MIDI_InvalidTrack,
+ MIDI_NoMemory,
+ MIDI_DPMI_Error
+ };
+
+
+#define MIDI_PASS_THROUGH 1
+#define MIDI_DONT_PLAY 0
+
+#define MIDI_MaxVolume 255
+
+extern char MIDI_PatchMap[ 128 ];
+
+typedef struct
+{
+ void (*NoteOff)(int32_t channel, int32_t key, int32_t velocity);
+ void (*NoteOn)(int32_t channel, int32_t key, int32_t velocity);
+ void (*PolyAftertouch)(int32_t channel, int32_t key, int32_t pressure);
+ void (*ControlChange)(int32_t channel, int32_t number, int32_t value);
+ void (*ProgramChange)(int32_t channel, int32_t program);
+ void (*ChannelAftertouch)(int32_t channel, int32_t pressure);
+ void (*PitchBend)(int32_t channel, int32_t lsb, int32_t msb);
+ void (*FinishBuffer)(void);
+} midifuncs;
+
+void MIDI_RerouteMidiChannel( int32_t channel, int32_t ( *function )( int32_t event, int32_t c1, int32_t c2 ) );
+int32_t MIDI_AllNotesOff( void );
+void MIDI_SetUserChannelVolume( int32_t channel, int32_t volume );
+void MIDI_ResetUserChannelVolume( void );
+int32_t MIDI_Reset( void );
+int32_t MIDI_SetVolume( int32_t volume );
+int32_t MIDI_GetVolume( void );
+void MIDI_SetMidiFuncs( midifuncs *funcs );
+void MIDI_SetContext( int32_t context );
+int32_t MIDI_GetContext( void );
+void MIDI_SetLoopFlag( int32_t loopflag );
+void MIDI_ContinueSong( void );
+void MIDI_PauseSong( void );
+int32_t MIDI_SongPlaying( void );
+void MIDI_StopSong( void );
+int32_t MIDI_PlaySong( char *song, int32_t loopflag );
+void MIDI_SetTempo( int32_t tempo );
+int32_t MIDI_GetTempo( void );
+void MIDI_SetSongTick( uint32_t PositionInTicks );
+void MIDI_SetSongTime( uint32_t milliseconds );
+void MIDI_SetSongPosition( int32_t measure, int32_t beat, int32_t tick );
+void MIDI_GetSongPosition( songposition *pos );
+void MIDI_GetSongLength( songposition *pos );
+void MIDI_LoadTimbres( void );
+void MIDI_MusicMix( char *buffer, int length );
+void MIDI_UpdateMusic(void);
+void MIDI_SetDivision( int32_t division );
+
+#endif
diff --git a/source/blood/src/mirrors.cpp b/source/blood/src/mirrors.cpp
new file mode 100644
index 000000000..8d178a8ec
--- /dev/null
+++ b/source/blood/src/mirrors.cpp
@@ -0,0 +1,488 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#ifdef POLYMER
+#include "polymer.h"
+#endif
+#include "common_game.h"
+#include "blood.h"
+#include "db.h"
+#include "gameutil.h"
+#include "loadsave.h"
+#include "player.h"
+#include "trig.h"
+#include "view.h"
+#include "warp.h"
+
+int mirrorcnt, mirrorsector, mirrorwall[4];
+
+typedef struct
+{
+ int at0;
+ int at4;
+ int at8;
+ int atc;
+ int at10;
+ int at14;
+} MIRROR;
+
+MIRROR mirror[16];
+
+#ifdef POLYMER
+void PolymerRORCallback(int16_t sectnum, int16_t wallnum, int8_t rorstat, int16_t* msectnum, int32_t* gx, int32_t* gy, int32_t* gz)
+{
+ UNREFERENCED_PARAMETER(wallnum);
+ int nMirror;
+ switch (rorstat)
+ {
+ case 1:
+ nMirror = sector[sectnum].ceilingpicnum-4080;
+ *msectnum = mirror[nMirror].at4;
+ *gx += mirror[nMirror].at8;
+ *gy += mirror[nMirror].atc;
+ *gz += mirror[nMirror].at10;
+ break;
+ case 2:
+ nMirror = sector[sectnum].floorpicnum-4080;
+ *msectnum = mirror[nMirror].at4;
+ *gx += mirror[nMirror].at8;
+ *gy += mirror[nMirror].atc;
+ *gz += mirror[nMirror].at10;
+ break;
+ }
+}
+#endif
+
+void InitMirrors(void)
+{
+
+#ifdef USE_OPENGL
+ r_rortexture = 4080;
+ r_rortexturerange = 16;
+#ifdef POLYMER
+ polymer_setrorcallback(PolymerRORCallback);
+#endif // POLYMER
+
+#endif
+ mirrorcnt = 0;
+ tilesiz[504].x = 0;
+ tilesiz[504].y = 0;
+ tileDelete(504);
+
+ for(int i = 0; i < 16; i++)
+ tilesiz[4080+i].x = 0, tilesiz[4080+i].y = 0;
+ for (int i = numwalls - 1; i >= 0; i--)
+ {
+ if (mirrorcnt == 16)
+ break;
+ int nTile = 4080+mirrorcnt;
+ if (wall[i].overpicnum == 504)
+ {
+ if (wall[i].extra > 0 && wall[i].lotag == 501)
+ {
+ wall[i].overpicnum = nTile;
+ mirror[mirrorcnt].at14 = i;
+ mirror[mirrorcnt].at0 = 0;
+ wall[i].cstat |= 32;
+ int tmp = xwall[wall[i].extra].data;
+ int j;
+ for (j = numwalls - 1; j >= 0; j--)
+ {
+ if (j == i)
+ continue;
+ if (wall[j].extra > 0 && wall[j].lotag == 501)
+ {
+ if (tmp != xwall[wall[j].extra].data)
+ continue;
+ wall[i].hitag = j;
+ wall[j].hitag = i;
+ mirror[mirrorcnt].at4 = j;
+ break;
+ }
+ }
+ if (j < 0)
+ ThrowError("wall[%d] has no matching wall link! (data=%d)\n", i, tmp);
+ mirrorcnt++;
+ }
+ continue;
+ }
+ if (wall[i].picnum == 504)
+ {
+ mirror[mirrorcnt].at4 = i;
+ mirror[mirrorcnt].at14 = i;
+ wall[i].picnum = nTile;
+ mirror[mirrorcnt].at0 = 0;
+ wall[i].cstat |= 32;
+ mirrorcnt++;
+ continue;
+ }
+ }
+ for (int i = numsectors - 1; i >= 0; i--)
+ {
+ if (mirrorcnt >= 15)
+ break;
+
+ if (sector[i].floorpicnum == 504)
+ {
+ int nLink = gUpperLink[i];
+ if (nLink < 0)
+ continue;
+ int nLink2 = sprite[nLink].owner & 0xfff;
+ int j = sprite[nLink2].sectnum;
+ if (sector[j].ceilingpicnum != 504)
+ ThrowError("Lower link sector %d doesn't have mirror picnum\n", j);
+ mirror[mirrorcnt].at0 = 2;
+ mirror[mirrorcnt].at8 = sprite[nLink2].x-sprite[nLink].x;
+ mirror[mirrorcnt].atc = sprite[nLink2].y-sprite[nLink].y;
+ mirror[mirrorcnt].at10 = sprite[nLink2].z-sprite[nLink].z;
+ mirror[mirrorcnt].at14 = i;
+ mirror[mirrorcnt].at4 = j;
+ sector[i].floorpicnum = 4080+mirrorcnt;
+ mirrorcnt++;
+ mirror[mirrorcnt].at0 = 1;
+ mirror[mirrorcnt].at8 = sprite[nLink].x-sprite[nLink2].x;
+ mirror[mirrorcnt].atc = sprite[nLink].y-sprite[nLink2].y;
+ mirror[mirrorcnt].at10 = sprite[nLink].z-sprite[nLink2].z;
+ mirror[mirrorcnt].at14 = j;
+ mirror[mirrorcnt].at4 = i;
+ sector[j].ceilingpicnum = 4080+mirrorcnt;
+ mirrorcnt++;
+ }
+ }
+ mirrorsector = numsectors;
+ for (int i = 0; i < 4; i++)
+ {
+ mirrorwall[i] = numwalls+i;
+ wall[mirrorwall[i]].picnum = 504;
+ wall[mirrorwall[i]].overpicnum = 504;
+ wall[mirrorwall[i]].cstat = 0;
+ wall[mirrorwall[i]].nextsector = -1;
+ wall[mirrorwall[i]].nextwall = -1;
+ wall[mirrorwall[i]].point2 = numwalls;
+ }
+ wall[mirrorwall[3]].point2 = mirrorwall[0];
+ sector[mirrorsector].ceilingpicnum = 504;
+ sector[mirrorsector].floorpicnum = 504;
+ sector[mirrorsector].wallptr = mirrorwall[0];
+ sector[mirrorsector].wallnum = 4;
+}
+
+void TranslateMirrorColors(int nShade, int nPalette)
+{
+ if (videoGetRenderMode() != REND_CLASSIC)
+ return;
+ videoBeginDrawing();
+ nShade = ClipRange(nShade, 0, 63);
+ char *pMap = palookup[nPalette] + (nShade<<8);
+ extern intptr_t frameplace;
+ char *pFrame = (char*)frameplace;
+ unsigned int nPixels = xdim*ydim;
+ for (unsigned int i = 0; i < nPixels; i++, pFrame++)
+ {
+ *pFrame = pMap[*pFrame];
+ }
+ videoEndDrawing();
+}
+
+void sub_5571C(char mode)
+{
+ for (int i = mirrorcnt-1; i >= 0; i--)
+ {
+ int nTile = 4080+i;
+ if (TestBitString(gotpic, nTile))
+ {
+ switch (mirror[i].at0)
+ {
+ case 1:
+ if (mode)
+ sector[mirror[i].at14].ceilingstat |= 1;
+ else
+ sector[mirror[i].at14].ceilingstat &= ~1;
+ break;
+ case 2:
+ if (mode)
+ sector[mirror[i].at14].floorstat |= 1;
+ else
+ sector[mirror[i].at14].floorstat &= ~1;
+ break;
+ }
+ }
+ }
+}
+
+void sub_557C4(int x, int y, int interpolation)
+{
+ if (spritesortcnt == 0) return;
+ int nViewSprites = spritesortcnt-1;
+ for (int nTSprite = nViewSprites; nTSprite >= 0; nTSprite--)
+ {
+ uspritetype *pTSprite = &tsprite[nTSprite];
+ pTSprite->xrepeat = pTSprite->yrepeat = 0;
+ }
+ for (int i = mirrorcnt-1; i >= 0; i--)
+ {
+ int nTile = 4080+i;
+ if (TestBitString(gotpic, nTile))
+ {
+ if (mirror[i].at0 == 1 || mirror[i].at0 == 2)
+ {
+ int nSector = mirror[i].at4;
+ int nSector2 = mirror[i].at14;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite == gView->pSprite)
+ continue;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int zCeil, zFloor;
+ getzsofslope(nSector, pSprite->x, pSprite->y, &zCeil, &zFloor);
+ if (pSprite->statnum == 6 && (top < zCeil || bottom > zFloor))
+ {
+ int j = i;
+ if (mirror[i].at0 == 2)
+ j++;
+ else
+ j--;
+ int dx = mirror[j].at8;
+ int dy = mirror[j].atc;
+ int dz = mirror[j].at10;
+ uspritetype *pTSprite = &tsprite[spritesortcnt];
+ memset(pTSprite, 0, sizeof(uspritetype));
+ pTSprite->type = pSprite->type;
+ pTSprite->index = pSprite->index;
+ pTSprite->sectnum = nSector2;
+ pTSprite->x = pSprite->x+dx;
+ pTSprite->y = pSprite->y+dy;
+ pTSprite->z = pSprite->z+dz;
+ pTSprite->ang = pSprite->ang;
+ pTSprite->picnum = pSprite->picnum;
+ pTSprite->shade = pSprite->shade;
+ pTSprite->pal = pSprite->pal;
+ pTSprite->xrepeat = pSprite->xrepeat;
+ pTSprite->yrepeat = pSprite->yrepeat;
+ pTSprite->xoffset = pSprite->xoffset;
+ pTSprite->yoffset = pSprite->yoffset;
+ pTSprite->cstat = pSprite->cstat;
+ pTSprite->statnum = 0;
+ pTSprite->owner = pSprite->index;
+ pTSprite->extra = pSprite->extra;
+ pTSprite->hitag = pSprite->hitag|0x200;
+ LOCATION *pLocation = &gPrevSpriteLoc[pSprite->index];
+ pTSprite->x = dx+interpolate(pLocation->x, pSprite->x, interpolation);
+ pTSprite->y = dy+interpolate(pLocation->y, pSprite->y, interpolation);
+ pTSprite->z = dz+interpolate(pLocation->z, pSprite->z, interpolation);
+ pTSprite->ang = pLocation->ang+mulscale16(((pSprite->ang-pLocation->ang+1024)&2047)-1024,interpolation);
+ spritesortcnt++;
+ }
+ }
+ }
+ }
+ }
+ for (int nTSprite = spritesortcnt-1; nTSprite >= nViewSprites; nTSprite--)
+ {
+ uspritetype *pTSprite = &tsprite[nTSprite];
+ int nAnim = 0;
+ switch (picanm[pTSprite->picnum].extra&7)
+ {
+ case 1:
+ {
+ int dX = x - pTSprite->x;
+ int dY = y - pTSprite->y;
+ RotateVector(&dX, &dY, 128 - pTSprite->ang);
+ nAnim = GetOctant(dX, dY);
+ if (nAnim <= 4)
+ {
+ pTSprite->cstat &= ~4;
+ }
+ else
+ {
+ nAnim = 8 - nAnim;
+ pTSprite->cstat |= 4;
+ }
+ break;
+ }
+ case 2:
+ {
+ int dX = x - pTSprite->x;
+ int dY = y - pTSprite->y;
+ RotateVector(&dX, &dY, 128 - pTSprite->ang);
+ nAnim = GetOctant(dX, dY);
+ break;
+ }
+ }
+ while (nAnim > 0)
+ {
+ pTSprite->picnum += picanm[pTSprite->picnum].num+1;
+ nAnim--;
+ }
+ }
+}
+
+void DrawMirrors(int x, int y, int z, fix16_t a, fix16_t horiz)
+{
+ if (videoGetRenderMode() == REND_POLYMER)
+ return;
+ for (int i = mirrorcnt - 1; i >= 0; i--)
+ {
+ int nTile = 4080+i;
+ if (TestBitString(gotpic, nTile))
+ {
+ ClearBitString(gotpic, nTile);
+ switch (mirror[i].at0)
+ {
+ case 0:
+ {
+ int nWall = mirror[i].at4;
+ int nSector = sectorofwall(nWall);
+ walltype *pWall = &wall[nWall];
+ int nNextWall = pWall->nextwall;
+ int nNextSector = pWall->nextsector;
+ pWall->nextwall = mirrorwall[0];
+ pWall->nextsector = mirrorsector;
+ wall[mirrorwall[0]].nextwall = nWall;
+ wall[mirrorwall[0]].nextsector = nSector;
+ wall[mirrorwall[0]].x = wall[pWall->point2].x;
+ wall[mirrorwall[0]].y = wall[pWall->point2].y;
+ wall[mirrorwall[1]].x = pWall->x;
+ wall[mirrorwall[1]].y = pWall->y;
+ wall[mirrorwall[2]].x = wall[mirrorwall[1]].x+(wall[mirrorwall[1]].x-wall[mirrorwall[0]].x)*16;
+ wall[mirrorwall[2]].y = wall[mirrorwall[1]].y+(wall[mirrorwall[1]].y-wall[mirrorwall[0]].y)*16;
+ wall[mirrorwall[3]].x = wall[mirrorwall[0]].x+(wall[mirrorwall[0]].x-wall[mirrorwall[1]].x)*16;
+ wall[mirrorwall[3]].y = wall[mirrorwall[0]].y+(wall[mirrorwall[0]].y-wall[mirrorwall[1]].y)*16;
+ sector[mirrorsector].floorz = sector[nSector].floorz;
+ sector[mirrorsector].ceilingz = sector[nSector].ceilingz;
+ int cx, cy, ca;
+ if (pWall->lotag == 501)
+ {
+ cx = x - (wall[pWall->hitag].x-wall[pWall->point2].x);
+ cy = y - (wall[pWall->hitag].y-wall[pWall->point2].y);
+ ca = a;
+ }
+ else
+ {
+ //renderPrepareMirror(x,y, fix16_from_int(a),nWall,&cx,&cy,&ca);
+ renderPrepareMirrorOld(x,y,z,a,horiz,nWall,mirrorsector,&cx,&cy,&ca);
+ }
+ renderDrawRoomsQ16(cx, cy, z, ca,horiz,mirrorsector|MAXSECTORS);
+ viewProcessSprites(cx,cy,z);
+ renderDrawMasks();
+ if (pWall->lotag != 501)
+ renderCompleteMirrorOld();
+ if (wall[nWall].pal != 0 || wall[nWall].shade != 0)
+ TranslateMirrorColors(wall[nWall].shade, wall[nWall].pal);
+ pWall->nextwall = nNextWall;
+ pWall->nextsector = nNextSector;
+ return;
+ }
+ case 1:
+ {
+#ifdef USE_OPENGL
+ r_rorphase = 1;
+#endif
+ int nSector = mirror[i].at4;
+ renderDrawRoomsQ16(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, a, horiz, nSector|MAXSECTORS);
+ viewProcessSprites(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10);
+ short fstat = sector[nSector].floorstat;
+ sector[nSector].floorstat |= 1;
+ renderDrawMasks();
+ sector[nSector].floorstat = fstat;
+ for (int i = 0; i < 16; i++)
+ ClearBitString(gotpic, 4080+i);
+#ifdef USE_OPENGL
+ r_rorphase = 0;
+#endif
+ return;
+ }
+ case 2:
+ {
+#ifdef USE_OPENGL
+ r_rorphase = 1;
+#endif
+ int nSector = mirror[i].at4;
+ renderDrawRoomsQ16(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, a, horiz, nSector|MAXSECTORS);
+ viewProcessSprites(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10);
+ short cstat = sector[nSector].ceilingstat;
+ sector[nSector].ceilingstat |= 1;
+ renderDrawMasks();
+ sector[nSector].ceilingstat = cstat;
+ for (int i = 0; i < 16; i++)
+ ClearBitString(gotpic, 4080+i);
+#ifdef USE_OPENGL
+ r_rorphase = 0;
+#endif
+ return;
+ }
+ }
+ }
+ }
+}
+
+class MirrorLoadSave : public LoadSave {
+public:
+ void Load(void);
+ void Save(void);
+};
+
+static MirrorLoadSave *myLoadSave;
+
+void MirrorLoadSave::Load(void)
+{
+ Read(&mirrorcnt,sizeof(mirrorcnt));
+ Read(&mirrorsector,sizeof(mirrorsector));
+ Read(mirror, sizeof(mirror));
+ Read(mirrorwall, sizeof(mirrorwall));
+ tilesiz[504].x = 0;
+ tilesiz[504].y = 0;
+
+ for (int i = 0; i < 16; i++)
+ tilesiz[4080 + i].x = 0, tilesiz[4080 + i].y = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ wall[mirrorwall[i]].picnum = 504;
+ wall[mirrorwall[i]].overpicnum = 504;
+ wall[mirrorwall[i]].point2 = numwalls;
+ wall[mirrorwall[i]].cstat = 0;
+ wall[mirrorwall[i]].nextsector = -1;
+ wall[mirrorwall[i]].nextwall = -1;
+ }
+ wall[mirrorwall[3]].point2 = mirrorwall[0];
+ sector[mirrorsector].ceilingpicnum = 504;
+ sector[mirrorsector].floorpicnum = 504;
+ sector[mirrorsector].wallptr = mirrorwall[0];
+ sector[mirrorsector].wallnum = 4;
+}
+
+void MirrorLoadSave::Save(void)
+{
+ Write(&mirrorcnt,sizeof(mirrorcnt));
+ Write(&mirrorsector,sizeof(mirrorsector));
+ Write(mirror, sizeof(mirror));
+ Write(mirrorwall, sizeof(mirrorwall));
+}
+
+void MirrorLoadSaveConstruct(void)
+{
+ myLoadSave = new MirrorLoadSave();
+}
diff --git a/source/blood/src/mirrors.h b/source/blood/src/mirrors.h
new file mode 100644
index 000000000..4300dfe7c
--- /dev/null
+++ b/source/blood/src/mirrors.h
@@ -0,0 +1,28 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "fix16.h"
+void InitMirrors(void);
+void sub_5571C(char mode);
+void sub_557C4(int x, int y, int interpolation);
+void DrawMirrors(int x, int y, int z, fix16_t a, fix16_t horiz);
diff --git a/source/blood/src/misc.cpp b/source/blood/src/misc.cpp
new file mode 100644
index 000000000..cc3e62503
--- /dev/null
+++ b/source/blood/src/misc.cpp
@@ -0,0 +1,141 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include "common_game.h"
+
+#include "misc.h"
+
+void *ResReadLine(char *buffer, unsigned int nBytes, void **pRes)
+{
+ unsigned int i;
+ char ch;
+ if (!pRes || !*pRes || *((char*)*pRes) == 0)
+ return NULL;
+ for (i = 0; i < nBytes; i++)
+ {
+ ch = *((char*)*pRes);
+ if(ch == 0 || ch == '\n')
+ break;
+ buffer[i] = ch;
+ *pRes = ((char*)*pRes)+1;
+ }
+ if (*((char*)*pRes) == '\n' && i < nBytes)
+ {
+ ch = *((char*)*pRes);
+ buffer[i] = ch;
+ *pRes = ((char*)*pRes)+1;
+ i++;
+ }
+ else
+ {
+ while (true)
+ {
+ ch = *((char*)*pRes);
+ if (ch == 0 || ch == '\n')
+ break;
+ *pRes = ((char*)*pRes)+1;
+ }
+ if (*((char*)*pRes) == '\n')
+ *pRes = ((char*)*pRes)+1;
+ }
+ if (i < nBytes)
+ buffer[i] = 0;
+ return *pRes;
+}
+
+bool FileRead(FILE *handle, void *buffer, unsigned int size)
+{
+ return fread(buffer, 1, size, handle) == size;
+}
+
+bool FileWrite(FILE *handle, void *buffer, unsigned int size)
+{
+ return fwrite(buffer, 1, size, handle) == size;
+}
+
+bool FileLoad(const char *name, void *buffer, unsigned int size)
+{
+ dassert(buffer != NULL);
+
+ FILE *handle = fopen(name, "rb");
+ if (!handle)
+ return false;
+
+ unsigned int nread = fread(buffer, 1, size, handle);
+ fclose(handle);
+ return nread == size;
+}
+
+int FileLength(FILE *handle)
+{
+ if (!handle)
+ return 0;
+ int nPos = ftell(handle);
+ fseek(handle, 0, SEEK_END);
+ int nLength = ftell(handle);
+ fseek(handle, nPos, SEEK_SET);
+ return nLength;
+}
+
+unsigned int randSeed = 1;
+
+unsigned int qrand(void)
+{
+ if (randSeed&0x80000000)
+ randSeed = ((randSeed<<1)^0x20000004)|0x1;
+ else
+ randSeed = randSeed<<1;
+ return randSeed&0x7fff;
+}
+
+int wRandSeed = 1;
+
+int wrand(void)
+{
+ wRandSeed = (wRandSeed*1103515245)+12345;
+ return (wRandSeed>>16)&0x7fff;
+}
+
+void wsrand(int seed)
+{
+ wRandSeed = seed;
+}
+
+void ChangeExtension(char *pzFile, const char *pzExt)
+{
+#if 0
+ char drive[BMAX_PATH];
+ char dir[BMAX_PATH];
+ char filename[BMAX_PATH];
+ _splitpath(pzFile, drive, dir, filename, NULL);
+ _makepath(pzFile, drive, dir, filename, pzExt);
+#else
+ char *pDot = strrchr(pzFile, '.');
+ if (!pDot)
+ pDot = pzFile + strlen(pzFile);
+ else
+ *pDot = 0;
+ strcat(pDot, pzExt);
+#endif
+}
diff --git a/source/blood/src/misc.h b/source/blood/src/misc.h
new file mode 100644
index 000000000..ea6cd367b
--- /dev/null
+++ b/source/blood/src/misc.h
@@ -0,0 +1,32 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+void *ResReadLine(char *buffer, unsigned int nBytes, void **pRes);
+bool FileRead(FILE *, void *, unsigned int);
+bool FileWrite(FILE *, void *, unsigned int);
+bool FileLoad(const char *, void *, unsigned int);
+int FileLength(FILE *);
+unsigned int qrand(void);
+int wrand(void);
+void wsrand(int);
+void ChangeExtension(char *pzFile, const char *pzExt);
diff --git a/source/blood/src/mpu401.cpp b/source/blood/src/mpu401.cpp
new file mode 100644
index 000000000..ffa022e77
--- /dev/null
+++ b/source/blood/src/mpu401.cpp
@@ -0,0 +1,497 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+/**********************************************************************
+ module: MPU401.C
+
+ author: James R. Dose
+ date: January 1, 1994
+
+ Low level routines to support sending of MIDI data to MPU401
+ compatible MIDI interfaces.
+
+ (c) Copyright 1994 James R. Dose. All Rights Reserved.
+**********************************************************************/
+
+// This object is shared by all Build games with MIDI playback!
+
+#include "mpu401.h"
+#include "compat.h"
+#include "pragmas.h"
+
+#define NEED_MMSYSTEM_H
+#include "windows_inc.h"
+
+static HMIDISTRM hmido = (HMIDISTRM)-1;
+static MIDIOUTCAPS midicaps;
+static DWORD mididevice = -1;
+
+#define PAD(x) ((((x)+3)&(~3)))
+
+#define BUFFERLEN (32*4*4)
+#define NUMBUFFERS 6
+static char eventbuf[NUMBUFFERS][BUFFERLEN];
+static int32_t eventcnt[NUMBUFFERS];
+static MIDIHDR bufferheaders[NUMBUFFERS];
+int32_t _MPU_CurrentBuffer = 0;
+int32_t _MPU_BuffersWaiting = 0;
+
+extern uint32_t _MIDI_GlobalPositionInTicks;
+uint32_t _MPU_LastEvent=0;
+
+#define MIDI_NOTE_OFF 0x80
+#define MIDI_NOTE_ON 0x90
+#define MIDI_POLY_AFTER_TCH 0xA0
+#define MIDI_CONTROL_CHANGE 0xB0
+#define MIDI_PROGRAM_CHANGE 0xC0
+#define MIDI_AFTER_TOUCH 0xD0
+#define MIDI_PITCH_BEND 0xE0
+#define MIDI_META_EVENT 0xFF
+#define MIDI_END_OF_TRACK 0x2F
+#define MIDI_TEMPO_CHANGE 0x51
+#define MIDI_MONO_MODE_ON 0x7E
+#define MIDI_ALL_NOTES_OFF 0x7B
+
+
+/**********************************************************************
+
+ Memory locked functions:
+
+**********************************************************************/
+
+
+void MPU_FinishBuffer(int32_t buffer)
+{
+ if (!eventcnt[buffer]) return;
+ ZeroMemory(&bufferheaders[buffer], sizeof(MIDIHDR));
+ bufferheaders[buffer].lpData = eventbuf[buffer];
+ bufferheaders[buffer].dwBufferLength =
+ bufferheaders[buffer].dwBytesRecorded = eventcnt[buffer];
+ midiOutPrepareHeader((HMIDIOUT)hmido, &bufferheaders[buffer], sizeof(MIDIHDR));
+ midiStreamOut(hmido, &bufferheaders[buffer], sizeof(MIDIHDR));
+// printf("Sending %d bytes (buffer %d)\n",eventcnt[buffer],buffer);
+ _MPU_BuffersWaiting++;
+}
+
+void MPU_BeginPlayback(void)
+{
+ _MPU_LastEvent = _MIDI_GlobalPositionInTicks;
+ if (hmido != (HMIDISTRM)-1) midiStreamRestart(hmido);
+}
+
+void MPU_Pause(void)
+{
+ if (hmido != (HMIDISTRM)-1) midiStreamPause(hmido);
+}
+
+void MPU_Unpause(void)
+{
+ if (hmido != (HMIDISTRM)-1) midiStreamRestart(hmido);
+}
+
+
+void CALLBACK MPU_MIDICallback(HMIDIOUT handle, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+ int32_t i;
+
+ UNREFERENCED_PARAMETER(dwInstance);
+ UNREFERENCED_PARAMETER(dwParam2);
+
+ switch (uMsg)
+ {
+ case MOM_DONE:
+ midiOutUnprepareHeader((HMIDIOUT)handle, (MIDIHDR *)dwParam1, sizeof(MIDIHDR));
+ for (i=0; i BUFFERLEN)
+ {
+ // buffer over-full
+ nextbuffer = MPU_GetNextBuffer();
+ if (nextbuffer < 0)
+ {
+// printf("All buffers full!\n");
+ return;
+ }
+ MPU_FinishBuffer(_MPU_CurrentBuffer);
+ _MPU_CurrentBuffer = nextbuffer;
+ }
+
+ p = eventbuf[_MPU_CurrentBuffer] + eventcnt[_MPU_CurrentBuffer];
+ ((int32_t *)p)[0] = _MIDI_GlobalPositionInTicks - _MPU_LastEvent;
+ ((int32_t *)p)[1] = 0;
+ ((int32_t *)p)[2] = (MEVT_SHORTMSG << 24) | ((*((int32_t *)data)) & masks[count-1]);
+ eventcnt[_MPU_CurrentBuffer] += 12;
+ }
+ else
+ {
+ padded = PAD(count);
+ if (eventcnt[_MPU_CurrentBuffer] + 12 + padded > BUFFERLEN)
+ {
+ // buffer over-full
+ nextbuffer = MPU_GetNextBuffer();
+ if (nextbuffer < 0)
+ {
+// printf("All buffers full!\n");
+ return;
+ }
+ MPU_FinishBuffer(_MPU_CurrentBuffer);
+ _MPU_CurrentBuffer = nextbuffer;
+ }
+
+ p = eventbuf[_MPU_CurrentBuffer] + eventcnt[_MPU_CurrentBuffer];
+ ((int32_t *)p)[0] = _MIDI_GlobalPositionInTicks - _MPU_LastEvent;
+ ((int32_t *)p)[1] = 0;
+ ((int32_t *)p)[2] = (MEVT_LONGMSG<<24) | (count & 0xffffffl);
+ p+=12; eventcnt[_MPU_CurrentBuffer] += 12;
+ for (; count>0; count--, padded--, eventcnt[_MPU_CurrentBuffer]++)
+ *(p++) = *(data++);
+ for (; padded>0; padded--, eventcnt[_MPU_CurrentBuffer]++)
+ *(p++) = 0;
+ }
+ _MPU_LastEvent = _MIDI_GlobalPositionInTicks;
+}
+
+
+/*---------------------------------------------------------------------
+ Function: MPU_SendMidiImmediate
+
+ Sends a MIDI message immediately to the the music device.
+---------------------------------------------------------------------*/
+void MPU_SendMidiImmediate(char *data, int32_t count)
+{
+ MIDIHDR mhdr;
+ static int32_t masks[3] = { 0x00ffffffl, 0x0000ffffl, 0x000000ffl };
+
+ if (!count) return;
+ if (count<=3) midiOutShortMsg((HMIDIOUT)hmido, (*((int32_t *)data)) & masks[count-1]);
+ else
+ {
+ ZeroMemory(&mhdr, sizeof(mhdr));
+ mhdr.lpData = data;
+ mhdr.dwBufferLength = count;
+ midiOutPrepareHeader((HMIDIOUT)hmido, &mhdr, sizeof(MIDIHDR));
+ midiOutLongMsg((HMIDIOUT)hmido, &mhdr, sizeof(MIDIHDR));
+ while (!(mhdr.dwFlags & MHDR_DONE)) ;
+ midiOutUnprepareHeader((HMIDIOUT)hmido, &mhdr, sizeof(MIDIHDR));
+ }
+}
+
+
+/*---------------------------------------------------------------------
+ Function: MPU_Reset
+
+ Resets the MPU401 card.
+---------------------------------------------------------------------*/
+
+int32_t MPU_Reset
+(
+ void
+)
+
+{
+ midiStreamStop(hmido);
+ midiStreamClose(hmido);
+ hmido = (HMIDISTRM)-1;
+
+ return MPU_Ok;
+}
+
+
+/*---------------------------------------------------------------------
+ Function: MPU_Init
+
+ Detects and initializes the MPU401 card.
+---------------------------------------------------------------------*/
+
+int32_t MPU_Init
+(
+ int32_t addr
+)
+
+{
+ if (hmido != (HMIDISTRM)-1)
+ return MPU_Ok;
+
+ int32_t i;
+
+ for (i=0; iNoteOff = MPU_NoteOff;
+ Funcs->NoteOn = MPU_NoteOn;
+ Funcs->PolyAftertouch = MPU_PolyAftertouch;
+ Funcs->ControlChange = MPU_ControlChange;
+ Funcs->ProgramChange = MPU_ProgramChange;
+ Funcs->ChannelAftertouch = MPU_ChannelAftertouch;
+ Funcs->PitchBend = MPU_PitchBend;
+
+ MIDI_SetMidiFuncs(Funcs);
+
+ return MIDI_Ok;
+}
+
+void MUSIC_Update(void) { MIDI_UpdateMusic(); }
diff --git a/source/blood/src/network.cpp b/source/blood/src/network.cpp
new file mode 100644
index 000000000..bc7d924d8
--- /dev/null
+++ b/source/blood/src/network.cpp
@@ -0,0 +1,1428 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "mmulti.h"
+#include "pragmas.h"
+#ifndef NETCODE_DISABLE
+#include "enet/enet.h"
+#endif
+#include "compat.h"
+#include "config.h"
+#include "controls.h"
+#include "globals.h"
+#include "network.h"
+#include "menu.h"
+#include "player.h"
+#include "seq.h"
+#include "sound.h"
+#include "view.h"
+
+char packet[576];
+bool gStartNewGame = 0;
+PACKETMODE gPacketMode = PACKETMODE_1;
+int gNetFifoClock = 0;
+int gNetFifoTail = 0;
+int gNetFifoHead[8];
+int gPredictTail = 0;
+int gNetFifoMasterTail = 0;
+GINPUT gFifoInput[256][8];
+int myMinLag[8];
+int otherMinLag = 0;
+int myMaxLag = 0;
+unsigned int gChecksum[4];
+unsigned int gCheckFifo[256][8][4];
+int gCheckHead[8];
+int gSendCheckTail = 0;
+int gCheckTail = 0;
+int gInitialNetPlayers = 0;
+int gBufferJitter = 1;
+int gPlayerReady[8];
+int gSyncRate = 1;
+bool bNoResend = true;
+bool gRobust = false;
+bool bOutOfSync = false;
+bool ready2send = false;
+
+NETWORKMODE gNetMode = NETWORK_NONE;
+char gNetAddress[32];
+// PORT-TODO: Use different port?
+int gNetPort = kNetDefaultPort;
+
+const short word_1328AC = 0x214;
+
+struct struct28E3B0
+{
+ int at0;
+ int at4;
+ int at8;
+ int atc;
+ int at10;
+ int at14;
+ int at18;
+ char at1c;
+ int at1d;
+};
+
+struct28E3B0 byte_28E3B0;
+
+PKT_STARTGAME gPacketStartGame;
+
+#ifndef NETCODE_DISABLE
+ENetAddress gNetENetAddress;
+ENetHost *gNetENetServer;
+ENetHost *gNetENetClient;
+ENetPeer *gNetENetPeer;
+ENetPeer *gNetPlayerPeer[kMaxPlayers];
+bool gNetENetInit = false;
+
+#define kENetFifoSize 2048
+
+//ENetPacket *gNetServerPacketFifo[kENetFifoSize];
+//int gNetServerPacketHead, gNetServerPacketTail;
+ENetPacket *gNetPacketFifo[kENetFifoSize];
+int gNetPacketHead, gNetPacketTail;
+
+enum BLOOD_ENET_CHANNEL {
+ BLOOD_ENET_SERVICE = 0,
+ BLOOD_ENET_GAME,
+ BLOOD_ENET_CHANNEL_MAX
+};
+
+enum BLOOD_SERVICE {
+ BLOOD_SERVICE_CONNECTINFO = 0,
+ BLOOD_SERVICE_CONNECTID,
+ BLOOD_SERVICE_SENDTOID,
+};
+
+struct PKT_CONNECTINFO {
+ short numplayers;
+ short connecthead;
+ short connectpoint2[kMaxPlayers];
+};
+
+void netServerDisconnect(void)
+{
+ ENetEvent event;
+ if (gNetMode != NETWORK_SERVER)
+ return;
+ for (int p = 0; p < kMaxPlayers; p++)
+ if (gNetPlayerPeer[p])
+ {
+ bool bDisconnectStatus = false;
+ enet_peer_disconnect_later(gNetPlayerPeer[p], 0);
+ if (enet_host_service(gNetENetServer, &event, 3000) > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_DISCONNECT:
+ bDisconnectStatus = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!bDisconnectStatus)
+ enet_peer_reset(gNetPlayerPeer[p]);
+ gNetPlayerPeer[p] = NULL;
+ }
+}
+
+void netClientDisconnect(void)
+{
+ ENetEvent event;
+ if (gNetMode != NETWORK_CLIENT || gNetENetPeer == NULL)
+ return;
+ enet_peer_disconnect_later(gNetENetPeer, 0);
+ bool bDisconnectStatus = false;
+ if (enet_host_service(gNetENetClient, &event, 3000) > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_DISCONNECT:
+ bDisconnectStatus = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!bDisconnectStatus)
+ enet_peer_reset(gNetENetPeer);
+ gNetENetPeer = NULL;
+}
+#endif
+
+void netResetToSinglePlayer(void)
+{
+ myconnectindex = connecthead = 0;
+ gInitialNetPlayers = gNetPlayers = numplayers = 1;
+ connectpoint2[0] = -1;
+ gGameOptions.nGameType = 0;
+ gNetMode = NETWORK_NONE;
+ UpdateNetworkMenus();
+}
+
+void netSendPacket(int nDest, char *pBuffer, int nSize)
+{
+#ifndef NETCODE_DISABLE
+ if (gNetMode == NETWORK_NONE)
+ return;
+ netUpdate();
+
+ if (gNetMode == NETWORK_SERVER)
+ {
+ if (gNetPlayerPeer[nDest] != NULL)
+ {
+ ENetPacket *pNetPacket = enet_packet_create(NULL, nSize + 1, ENET_PACKET_FLAG_RELIABLE);
+ char *pPBuffer = (char*)pNetPacket->data;
+ PutPacketByte(pPBuffer, myconnectindex);
+ PutPacketBuffer(pPBuffer, pBuffer, nSize);
+ enet_peer_send(gNetPlayerPeer[nDest], BLOOD_ENET_GAME, pNetPacket);
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+ }
+ else
+ {
+ if (nDest == 0)
+ {
+ ENetPacket *pNetPacket = enet_packet_create(NULL, nSize + 1, ENET_PACKET_FLAG_RELIABLE);
+ char *pPBuffer = (char*)pNetPacket->data;
+ PutPacketByte(pPBuffer, myconnectindex);
+ PutPacketBuffer(pPBuffer, pBuffer, nSize);
+ enet_peer_send(gNetENetPeer, BLOOD_ENET_GAME, pNetPacket);
+ }
+ else
+ {
+ ENetPacket *pNetPacket = enet_packet_create(NULL, nSize + 3, ENET_PACKET_FLAG_RELIABLE);
+ char *pPBuffer = (char*)pNetPacket->data;
+ PutPacketByte(pPBuffer, BLOOD_SERVICE_SENDTOID);
+ PutPacketByte(pPBuffer, nDest);
+ PutPacketByte(pPBuffer, myconnectindex);
+ PutPacketBuffer(pPBuffer, pBuffer, nSize);
+ enet_peer_send(gNetENetPeer, BLOOD_ENET_SERVICE, pNetPacket);
+ }
+ enet_host_service(gNetENetClient, NULL, 0);
+ }
+
+ netUpdate();
+#endif
+}
+
+void netSendPacketAll(char *pBuffer, int nSize)
+{
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ if (p != myconnectindex)
+ netSendPacket(p, pBuffer, nSize);
+}
+
+void sub_79760(void)
+{
+ gNetFifoClock = gFrameClock = gGameClock = 0;
+ gNetFifoMasterTail = 0;
+ gPredictTail = 0;
+ gNetFifoTail = 0;
+ memset(gNetFifoHead, 0, sizeof(gNetFifoHead));
+ memset(gCheckFifo, 0, sizeof(gCheckFifo));
+ memset(myMinLag, 0, sizeof(myMinLag));
+ otherMinLag = 0;
+ myMaxLag = 0;
+ memset(gCheckHead, 0, sizeof(gCheckHead));
+ gSendCheckTail = 0;
+ gCheckTail = 0;
+ memset(&byte_28E3B0, 0, sizeof(byte_28E3B0));
+ bOutOfSync = 0;
+ gBufferJitter = 1;
+}
+
+void CalcGameChecksum(void)
+{
+ memset(gChecksum, 0, sizeof(gChecksum));
+ gChecksum[0] = wrand();
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ int *pBuffer = &gPlayer[p].at22;
+ int sum = 0;
+ int length = ((char*)&gPlayer[p+1]-(char*)pBuffer)/4;
+ while (length--)
+ {
+ sum += *pBuffer++;
+ }
+ gChecksum[1] ^= sum;
+ pBuffer = (int*)gPlayer[p].pSprite;
+ sum = 0;
+ length = sizeof(spritetype)/4;
+ while (length--)
+ {
+ sum += *pBuffer++;
+ }
+ gChecksum[2] ^= sum;
+ pBuffer = (int*)gPlayer[p].pXSprite;
+ sum = 0;
+ length = sizeof(XSPRITE)/4;
+ while (length--)
+ {
+ sum += *pBuffer++;
+ }
+ gChecksum[3] ^= sum;
+ }
+}
+
+void netCheckSync(void)
+{
+ char buffer[80];
+ if (gGameOptions.nGameType == 0)
+ return;
+ if (numplayers == 1)
+ return;
+ if (bOutOfSync)
+ return;
+ while (1)
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (gCheckTail >= gCheckHead[p])
+ return;
+ }
+
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (p != myconnectindex)
+ {
+ int status = memcmp(gCheckFifo[gCheckTail&255][p], gCheckFifo[gCheckTail&255][connecthead], 16);
+ if (status)
+ {
+ sprintf(buffer, "OUT OF SYNC (%d)", p);
+ char *pBuffer = buffer + strlen(buffer);
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ if (gCheckFifo[gCheckTail&255][p][i] != gCheckFifo[gCheckTail&255][connecthead][i])
+ pBuffer += sprintf(pBuffer, " %d", i);
+ }
+ viewSetErrorMessage(buffer);
+ bOutOfSync = 1;
+ }
+ }
+ }
+ gCheckTail++;
+ }
+}
+
+short netGetPacket(short *pSource, char *pMessage)
+{
+#ifndef NETCODE_DISABLE
+ if (gNetMode == NETWORK_NONE)
+ return 0;
+ netUpdate();
+ if (gNetPacketTail != gNetPacketHead)
+ {
+ ENetPacket *pEPacket = gNetPacketFifo[gNetPacketTail];
+ gNetPacketTail = (gNetPacketTail+1)%kENetFifoSize;
+ char *pPacket = (char*)pEPacket->data;
+ *pSource = GetPacketByte(pPacket);
+ int nSize = pEPacket->dataLength-1;
+ memcpy(pMessage, pPacket, nSize);
+ enet_packet_destroy(pEPacket);
+ netUpdate();
+ return nSize;
+ }
+ netUpdate();
+#endif
+ return 0;
+}
+
+void netGetPackets(void)
+{
+ short nPlayer;
+ short nSize;
+ char buffer[128];
+ while ((nSize = netGetPacket(&nPlayer, packet)) > 0)
+ {
+ char *pPacket = packet;
+ switch (GetPacketByte(pPacket))
+ {
+ case 0: // From master
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (p != myconnectindex)
+ {
+ GINPUT *pInput = &gFifoInput[gNetFifoHead[p]&255][p];
+ memset(pInput, 0, sizeof(GINPUT));
+ pInput->syncFlags.byte = GetPacketByte(pPacket);
+ pInput->forward = GetPacketWord(pPacket);
+ pInput->q16turn = GetPacketDWord(pPacket);
+ pInput->strafe = GetPacketWord(pPacket);
+ if (pInput->syncFlags.buttonChange)
+ pInput->buttonFlags.byte = GetPacketByte(pPacket);
+ if (pInput->syncFlags.keyChange)
+ pInput->keyFlags.word = GetPacketWord(pPacket);
+ if (pInput->syncFlags.useChange)
+ pInput->useFlags.byte = GetPacketByte(pPacket);
+ if (pInput->syncFlags.weaponChange)
+ pInput->newWeapon = GetPacketByte(pPacket);
+ if (pInput->syncFlags.mlookChange)
+ pInput->q16mlook = GetPacketDWord(pPacket);
+ gNetFifoHead[p]++;
+ }
+ else
+ {
+ SYNCFLAGS syncFlags;
+ syncFlags.byte = GetPacketByte(pPacket);
+ pPacket += 2+4+2;
+ if (syncFlags.buttonChange)
+ pPacket++;
+ if (syncFlags.keyChange)
+ pPacket+=2;
+ if (syncFlags.useChange)
+ pPacket++;
+ if (syncFlags.weaponChange)
+ pPacket++;
+ if (syncFlags.mlookChange)
+ pPacket+=4;
+ }
+ }
+ if (((gNetFifoHead[connecthead]-1)&15)==0)
+ {
+ for (int p = connectpoint2[connecthead]; p >= 0; p = connectpoint2[p])
+ {
+ int nLag = (signed char)GetPacketByte(pPacket);
+ if (p == myconnectindex)
+ otherMinLag = nLag;
+ }
+ }
+ while (pPacket < packet+nSize)
+ {
+ int checkSum[4];
+ GetPacketBuffer(pPacket, checkSum, sizeof(checkSum));
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (p != myconnectindex)
+ {
+ memcpy(gCheckFifo[gCheckHead[p]&255][p], checkSum, sizeof(checkSum));
+ gCheckHead[p]++;
+ }
+ }
+ }
+ break;
+ case 1: // From slave
+ {
+ GINPUT *pInput = &gFifoInput[gNetFifoHead[nPlayer]&255][nPlayer];
+ memset(pInput, 0, sizeof(GINPUT));
+ pInput->syncFlags.byte = GetPacketByte(pPacket);
+ pInput->forward = GetPacketWord(pPacket);
+ pInput->q16turn = GetPacketDWord(pPacket);
+ pInput->strafe = GetPacketWord(pPacket);
+ if (pInput->syncFlags.buttonChange)
+ pInput->buttonFlags.byte = GetPacketByte(pPacket);
+ if (pInput->syncFlags.keyChange)
+ pInput->keyFlags.word = GetPacketWord(pPacket);
+ if (pInput->syncFlags.useChange)
+ pInput->useFlags.byte = GetPacketByte(pPacket);
+ if (pInput->syncFlags.weaponChange)
+ pInput->newWeapon = GetPacketByte(pPacket);
+ if (pInput->syncFlags.mlookChange)
+ pInput->q16mlook = GetPacketDWord(pPacket);
+ gNetFifoHead[nPlayer]++;
+ while (pPacket < packet+nSize)
+ {
+ int checkSum[4];
+ GetPacketBuffer(pPacket, checkSum, sizeof(checkSum));
+ memcpy(gCheckFifo[gCheckHead[nPlayer]&255][nPlayer], checkSum, sizeof(checkSum));
+ gCheckHead[nPlayer]++;
+ }
+ break;
+ }
+ case 2:
+ {
+ if (nPlayer == connecthead && (gNetFifoHead[nPlayer]&15) == 0)
+ {
+ for (int p = connectpoint2[connecthead]; p >= 0; p = connectpoint2[p])
+ {
+ int nLag = (signed char)GetPacketByte(pPacket);
+ if (p == myconnectindex)
+ otherMinLag = nLag;
+ }
+ }
+ GINPUT *pInput = &gFifoInput[gNetFifoHead[nPlayer]&255][nPlayer];
+ memset(pInput, 0, sizeof(GINPUT));
+ pInput->syncFlags.byte = GetPacketByte(pPacket);
+ pInput->forward = GetPacketWord(pPacket);
+ pInput->q16turn = GetPacketDWord(pPacket);
+ pInput->strafe = GetPacketWord(pPacket);
+ if (pInput->syncFlags.buttonChange)
+ pInput->buttonFlags.byte = GetPacketByte(pPacket);
+ if (pInput->syncFlags.keyChange)
+ pInput->keyFlags.word = GetPacketWord(pPacket);
+ if (pInput->syncFlags.useChange)
+ pInput->useFlags.byte = GetPacketByte(pPacket);
+ if (pInput->syncFlags.weaponChange)
+ pInput->newWeapon = GetPacketByte(pPacket);
+ if (pInput->syncFlags.mlookChange)
+ pInput->q16mlook = GetPacketDWord(pPacket);
+ gNetFifoHead[nPlayer]++;
+ for (int i = gSyncRate; i > 1; i--)
+ {
+ GINPUT *pInput2 = &gFifoInput[gNetFifoHead[nPlayer]&255][nPlayer];
+ memcpy(pInput2, pInput, sizeof(GINPUT));
+ pInput2->keyFlags.word = 0;
+ pInput2->useFlags.byte = 0;
+ pInput2->newWeapon = 0;
+ pInput2->syncFlags.weaponChange = 0;
+ gNetFifoHead[nPlayer]++;
+ }
+ while (pPacket < packet+nSize)
+ {
+ int checkSum[4];
+ GetPacketBuffer(pPacket, checkSum, sizeof(checkSum));
+ memcpy(gCheckFifo[gCheckHead[nPlayer]&255][nPlayer], checkSum, sizeof(checkSum));
+ gCheckHead[nPlayer]++;
+ }
+ break;
+ }
+ case 3:
+ pPacket += 4;
+ if (*pPacket != '/' || (*pPacket == 0 && *(pPacket+1) == 0) || (*(pPacket+1) >= '1' && *(pPacket+1) <= '8' && *(pPacket+1)-'1' == myconnectindex))
+ {
+ sprintf(buffer, "%s : %s", gProfile[nPlayer].name, pPacket);
+ viewSetMessage(buffer);
+ sndStartSample("DMRADIO", 128, -1);
+ }
+ break;
+ case 4:
+ sndStartSample(4400+GetPacketByte(pPacket), 128, 1, 0);
+ break;
+ case 7:
+ nPlayer = GetPacketDWord(pPacket);
+ dassert(nPlayer != myconnectindex);
+ netWaitForEveryone(0);
+ netPlayerQuit(nPlayer);
+ netWaitForEveryone(0);
+ break;
+ case 249:
+ nPlayer = GetPacketDWord(pPacket);
+ dassert(nPlayer != myconnectindex);
+ netPlayerQuit(nPlayer);
+ netWaitForEveryone(0);
+ break;
+ case 250:
+ gPlayerReady[nPlayer]++;
+ break;
+ case 251:
+ memcpy(&gProfile[nPlayer], pPacket, sizeof(PROFILE));
+ break;
+ case 252:
+ pPacket += 4;
+ memcpy(&gPacketStartGame, pPacket, sizeof(PKT_STARTGAME));
+ if (gPacketStartGame.version != word_1328AC)
+ ThrowError("\nThese versions of Blood cannot play together.\n");
+ gStartNewGame = 1;
+ break;
+ case 255:
+ keystatus[1] = 1;
+ break;
+ }
+ }
+}
+
+void netBroadcastPlayerLogoff(int nPlayer)
+{
+ if (numplayers < 2)
+ return;
+ netWaitForEveryone(0);
+ netPlayerQuit(nPlayer);
+ if (nPlayer != myconnectindex)
+ netWaitForEveryone(0);
+}
+
+void netBroadcastMyLogoff(bool bRestart)
+{
+ if (numplayers < 2)
+ return;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 7);
+ PutPacketDWord(pPacket, myconnectindex);
+ netSendPacketAll(packet, pPacket - packet);
+ netWaitForEveryone(0);
+ ready2send = 0;
+ gQuitGame = true;
+ if (bRestart)
+ gRestartGame = true;
+ netDeinitialize();
+ netResetToSinglePlayer();
+}
+
+void netBroadcastPlayerInfo(int nPlayer)
+{
+ PROFILE *pProfile = &gProfile[nPlayer];
+ strcpy(pProfile->name, szPlayerName);
+ pProfile->skill = gSkill;
+ pProfile->nAutoAim = gAutoAim;
+ pProfile->nWeaponSwitch = gWeaponSwitch;
+ if (numplayers < 2)
+ return;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 251);
+ PutPacketBuffer(pPacket, pProfile, sizeof(PROFILE));
+ netSendPacketAll(packet, pPacket-packet);
+}
+
+void netBroadcastNewGame(void)
+{
+ if (numplayers < 2)
+ return;
+ gPacketStartGame.version = word_1328AC;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 252);
+ PutPacketDWord(pPacket, myconnectindex);
+ PutPacketBuffer(pPacket, &gPacketStartGame, sizeof(PKT_STARTGAME));
+ netSendPacketAll(packet, pPacket-packet);
+}
+
+void netBroadcastTaunt(int nPlayer, int nTaunt)
+{
+ UNREFERENCED_PARAMETER(nPlayer);
+ if (numplayers > 1)
+ {
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 4);
+ PutPacketByte(pPacket, nTaunt);
+ netSendPacketAll(packet, pPacket-packet);
+ }
+ sndStartSample(4400+nTaunt, 128, 1, 0);
+}
+
+void netBroadcastMessage(int nPlayer, const char *pzMessage)
+{
+ if (numplayers > 1)
+ {
+ int nSize = strlen(pzMessage);
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 3);
+ PutPacketDWord(pPacket, nPlayer);
+ PutPacketBuffer(pPacket, pzMessage, nSize+1);
+ netSendPacketAll(packet, pPacket-packet);
+ }
+}
+
+void netWaitForEveryone(char a1)
+{
+ if (numplayers < 2)
+ return;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 250);
+ netSendPacketAll(packet, pPacket-packet);
+ gPlayerReady[myconnectindex]++;
+ int p;
+ do
+ {
+ if (keystatus[sc_Escape] && a1)
+ exit(0);
+ G_HandleAsync();
+ faketimerhandler();
+ for (p = connecthead; p >= 0; p = connectpoint2[p])
+ if (gPlayerReady[p] < gPlayerReady[myconnectindex])
+ break;
+ if (gRestartGame || gNetPlayers <= 1)
+ break;
+ } while (p >= 0);
+}
+
+void sub_7AC28(const char *pzString)
+{
+ if (numplayers < 2)
+ return;
+ if (pzString)
+ {
+ int nLength = strlen(pzString);
+ if (nLength > 0)
+ {
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 5);
+ PutPacketBuffer(pPacket, pzString, nLength+1);
+ netSendPacketAll(packet, pPacket-packet);
+ }
+ }
+}
+
+void netSendEmptyPackets(void)
+{
+ int nClock = gGameClock;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 254);
+ for (int i = 0; i < 8; i++)
+ {
+ if (nClock <= gGameClock)
+ {
+ nClock = gGameClock+4;
+ netSendPacketAll(packet, pPacket-packet);
+ }
+ }
+}
+
+void sub_7AD90(GINPUT *pInput)
+{
+ byte_28E3B0.at0 |= pInput->syncFlags.byte;
+ byte_28E3B0.at4 += pInput->forward;
+ byte_28E3B0.at8 += pInput->q16turn;
+ byte_28E3B0.atc += pInput->strafe;
+ byte_28E3B0.at10 |= pInput->buttonFlags.byte;
+ byte_28E3B0.at14 |= pInput->keyFlags.word;
+ byte_28E3B0.at18 |= pInput->useFlags.byte;
+ if (pInput->newWeapon)
+ byte_28E3B0.at1c = pInput->newWeapon;
+ byte_28E3B0.at1d = pInput->q16mlook;
+}
+
+void sub_7AE2C(GINPUT *pInput)
+{
+ pInput->syncFlags.byte = byte_28E3B0.at0;
+ pInput->forward = byte_28E3B0.at4;
+ pInput->q16turn = byte_28E3B0.at8;
+ pInput->strafe = byte_28E3B0.atc;
+ pInput->buttonFlags.byte = byte_28E3B0.at10;
+ pInput->keyFlags.word = byte_28E3B0.at14;
+ pInput->useFlags.byte = byte_28E3B0.at18;
+ pInput->newWeapon = byte_28E3B0.at1c;
+ pInput->q16mlook = byte_28E3B0.at1d;
+ memset(&byte_28E3B0, 0, sizeof(byte_28E3B0));
+}
+
+void netMasterUpdate(void)
+{
+ if (myconnectindex != connecthead)
+ return;
+ char v4 = 0;
+ do
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ if (gNetFifoMasterTail >= gNetFifoHead[p])
+ {
+ if (v4)
+ return;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 254);
+ for (; p >= 0; p = connectpoint2[p])
+ netSendPacket(p, packet, pPacket-packet);
+ return;
+ }
+ v4 = 1;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 0);
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ GINPUT *pInput = &gFifoInput[gNetFifoMasterTail&255][p];
+ if (pInput->buttonFlags.byte)
+ pInput->syncFlags.buttonChange = 1;
+ if (pInput->keyFlags.word)
+ pInput->syncFlags.keyChange = 1;
+ if (pInput->useFlags.byte)
+ pInput->syncFlags.useChange = 1;
+ if (pInput->newWeapon)
+ pInput->syncFlags.weaponChange = 1;
+ if (pInput->q16mlook)
+ pInput->syncFlags.mlookChange = 1;
+ PutPacketByte(pPacket, pInput->syncFlags.byte);
+ PutPacketWord(pPacket, pInput->forward);
+ PutPacketDWord(pPacket, pInput->q16turn);
+ PutPacketWord(pPacket, pInput->strafe);
+ if (pInput->syncFlags.buttonChange)
+ PutPacketByte(pPacket, pInput->buttonFlags.byte);
+ if (pInput->syncFlags.keyChange)
+ PutPacketWord(pPacket, pInput->keyFlags.word);
+ if (pInput->syncFlags.useChange)
+ PutPacketByte(pPacket, pInput->useFlags.byte);
+ if (pInput->syncFlags.weaponChange)
+ PutPacketByte(pPacket, pInput->newWeapon);
+ if (pInput->syncFlags.mlookChange)
+ PutPacketDWord(pPacket, pInput->q16mlook);
+ }
+ if ((gNetFifoMasterTail&15) == 0)
+ {
+ for (int p = connectpoint2[connecthead]; p >= 0; p = connectpoint2[p])
+ PutPacketByte(pPacket, ClipRange(myMinLag[p], -128, 127));
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ myMinLag[p] = 0x7fffffff;
+ }
+ while (gSendCheckTail != gCheckHead[myconnectindex])
+ {
+ PutPacketBuffer(pPacket, gCheckFifo[gSendCheckTail&255][myconnectindex], 16);
+ gSendCheckTail++;
+ }
+ for (int p = connectpoint2[connecthead]; p >= 0; p = connectpoint2[p])
+ netSendPacket(p, packet, pPacket-packet);
+ gNetFifoMasterTail++;
+ } while (1);
+}
+
+void netGetInput(void)
+{
+ if (numplayers > 1)
+ netGetPackets();
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ if (gNetFifoHead[myconnectindex]-200 > gNetFifoHead[p])
+ return;
+ ctrlGetInput();
+ sub_7AD90(&gInput);
+ if (gNetFifoHead[myconnectindex]&(gSyncRate-1))
+ {
+ GINPUT *pInput1 = &gFifoInput[gNetFifoHead[myconnectindex]&255][myconnectindex];
+ GINPUT *pInput2 = &gFifoInput[(gNetFifoHead[myconnectindex]-1)&255][myconnectindex];
+ memcpy(pInput1, pInput2, sizeof(GINPUT));
+ pInput1->keyFlags.word = 0;
+ pInput1->useFlags.byte = 0;
+ pInput1->newWeapon = 0;
+ pInput1->syncFlags.weaponChange = 0;
+ gNetFifoHead[myconnectindex]++;
+ return;
+ }
+ GINPUT *pInput = &gFifoInput[gNetFifoHead[myconnectindex]&255][myconnectindex];
+ sub_7AE2C(pInput);
+ memcpy(&gInput, pInput, sizeof(GINPUT));
+ gNetFifoHead[myconnectindex]++;
+ if (gGameOptions.nGameType == 0 || numplayers == 1)
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (p != myconnectindex)
+ {
+ GINPUT *pInput1 = &gFifoInput[(gNetFifoHead[p]-1)&255][p];
+ GINPUT *pInput2 = &gFifoInput[gNetFifoHead[p]&255][p];
+ memcpy(pInput2, pInput1, sizeof(GINPUT));
+ gNetFifoHead[p]++;
+ }
+ }
+ return;
+ }
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (p != myconnectindex)
+ {
+ int nLag = gNetFifoHead[myconnectindex]-1-gNetFifoHead[p];
+ myMinLag[p] = ClipHigh(nLag, myMinLag[p]);
+ myMaxLag = ClipLow(nLag, myMaxLag);
+ }
+ }
+ if (((gNetFifoHead[myconnectindex]-1)&15) == 0)
+ {
+ int t = myMaxLag-gBufferJitter;
+ myMaxLag = 0;
+ if (t > 0)
+ gBufferJitter += (3+t)>>2;
+ else if (t < 0)
+ gBufferJitter -= (1-t)>>2;
+ }
+ if (gPacketMode == PACKETMODE_2)
+ {
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 2);
+ if (((gNetFifoHead[myconnectindex]-1)&15) == 0)
+ {
+ if (myconnectindex == connecthead)
+ {
+ for (int p = connectpoint2[connecthead]; p >= 0; p = connectpoint2[p])
+ PutPacketByte(pPacket, ClipRange(myMinLag[p], -128, 127));
+ }
+ else
+ {
+ int t = myMinLag[connecthead]-otherMinLag;
+ if (klabs(t) > 2)
+ {
+ if (klabs(t) > 8)
+ {
+ if (t < 0)
+ t++;
+ t >>= 1;
+ }
+ else
+ t = ksgn(t);
+ gGameClock -= t<<2;
+ otherMinLag += t;
+ myMinLag[connecthead] -= t;
+ }
+ }
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ myMinLag[p] = 0x7fffffff;
+ }
+ if (gInput.buttonFlags.byte)
+ gInput.syncFlags.buttonChange = 1;
+ if (gInput.keyFlags.word)
+ gInput.syncFlags.keyChange = 1;
+ if (gInput.useFlags.byte)
+ gInput.syncFlags.useChange = 1;
+ if (gInput.newWeapon)
+ gInput.syncFlags.weaponChange = 1;
+ if (gInput.q16mlook)
+ gInput.syncFlags.mlookChange = 1;
+ PutPacketByte(pPacket, gInput.syncFlags.byte);
+ PutPacketWord(pPacket, gInput.forward);
+ PutPacketDWord(pPacket, gInput.q16turn);
+ PutPacketWord(pPacket, gInput.strafe);
+ if (gInput.syncFlags.buttonChange)
+ PutPacketByte(pPacket, gInput.buttonFlags.byte);
+ if (gInput.syncFlags.keyChange)
+ PutPacketWord(pPacket, gInput.keyFlags.word);
+ if (gInput.syncFlags.useChange)
+ PutPacketByte(pPacket, gInput.useFlags.byte);
+ if (gInput.syncFlags.weaponChange)
+ PutPacketByte(pPacket, gInput.newWeapon);
+ if (gInput.syncFlags.mlookChange)
+ PutPacketDWord(pPacket, gInput.q16mlook);
+ while (gSendCheckTail != gCheckHead[myconnectindex])
+ {
+ unsigned int *checkSum = gCheckFifo[gSendCheckTail&255][myconnectindex];
+ PutPacketBuffer(pPacket, checkSum, 16);
+ gSendCheckTail++;
+ }
+ netSendPacketAll(packet, pPacket-packet);
+ return;
+ }
+ if (myconnectindex != connecthead)
+ {
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 1);
+ if (gInput.buttonFlags.byte)
+ gInput.syncFlags.buttonChange = 1;
+ if (gInput.keyFlags.word)
+ gInput.syncFlags.keyChange = 1;
+ if (gInput.useFlags.byte)
+ gInput.syncFlags.useChange = 1;
+ if (gInput.newWeapon)
+ gInput.syncFlags.weaponChange = 1;
+ if (gInput.q16mlook)
+ gInput.syncFlags.mlookChange = 1;
+ PutPacketByte(pPacket, gInput.syncFlags.byte);
+ PutPacketWord(pPacket, gInput.forward);
+ PutPacketDWord(pPacket, gInput.q16turn);
+ PutPacketWord(pPacket, gInput.strafe);
+ if (gInput.syncFlags.buttonChange)
+ PutPacketByte(pPacket, gInput.buttonFlags.byte);
+ if (gInput.syncFlags.keyChange)
+ PutPacketWord(pPacket, gInput.keyFlags.word);
+ if (gInput.syncFlags.useChange)
+ PutPacketByte(pPacket, gInput.useFlags.byte);
+ if (gInput.syncFlags.weaponChange)
+ PutPacketByte(pPacket, gInput.newWeapon);
+ if (gInput.syncFlags.mlookChange)
+ PutPacketDWord(pPacket, gInput.q16mlook);
+ if (((gNetFifoHead[myconnectindex]-1)&15) == 0)
+ {
+ int t = myMinLag[connecthead]-otherMinLag;
+ if (klabs(t) > 2)
+ {
+ if (klabs(t) > 8)
+ {
+ if (t < 0)
+ t++;
+ t >>= 1;
+ }
+ else
+ t = ksgn(t);
+ gGameClock -= t<<2;
+ otherMinLag += t;
+ myMinLag[connecthead] -= t;
+ }
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ myMinLag[p] = 0x7fffffff;
+ }
+ while (gSendCheckTail != gCheckHead[myconnectindex])
+ {
+ PutPacketBuffer(pPacket, gCheckFifo[gSendCheckTail&255][myconnectindex], 16);
+ gSendCheckTail++;
+ }
+ netSendPacket(connecthead, packet, pPacket-packet);
+ return;
+ }
+ netMasterUpdate();
+}
+
+void netInitialize(bool bConsole)
+{
+ netDeinitialize();
+ memset(gPlayerReady, 0, sizeof(gPlayerReady));
+ sub_79760();
+#ifndef NETCODE_DISABLE
+ char buffer[128];
+ gNetENetServer = gNetENetClient = NULL;
+ //gNetServerPacketHead = gNetServerPacketTail = 0;
+ gNetPacketHead = gNetPacketTail = 0;
+ if (gNetMode == NETWORK_NONE)
+ {
+ netResetToSinglePlayer();
+ return;
+ }
+ if (enet_initialize() != 0)
+ {
+ initprintf("An error occurred while initializing ENet.\n");
+ netResetToSinglePlayer();
+ return;
+ }
+ if (gNetMode == NETWORK_SERVER)
+ {
+ memset(gNetPlayerPeer, 0, sizeof(gNetPlayerPeer));
+ ENetEvent event;
+ gNetENetAddress.host = ENET_HOST_ANY;
+ gNetENetAddress.port = gNetPort;
+ gNetENetServer = enet_host_create(&gNetENetAddress, 8, BLOOD_ENET_CHANNEL_MAX, 0, 0);
+ if (!gNetENetServer)
+ {
+ initprintf("An error occurred while trying to create an ENet server host.\n");
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ }
+
+ numplayers = 1;
+ // Wait for clients
+ if (!bConsole)
+ {
+ char buffer[128];
+ sprintf(buffer, "Waiting for players (%i\\%i)", numplayers, gNetPlayers);
+ viewLoadingScreen(2518, "Network Game", NULL, buffer);
+ }
+ while (numplayers < gNetPlayers)
+ {
+ handleevents();
+ if (quitevent)
+ {
+ netServerDisconnect();
+ QuitGame();
+ }
+ if (!bConsole && KB_KeyPressed(sc_Escape))
+ {
+ netServerDisconnect();
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ }
+ enet_host_service(gNetENetServer, NULL, 0);
+ if (enet_host_check_events(gNetENetServer, &event) > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_CONNECT:
+ {
+ char ipaddr[32];
+
+ enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr));
+ initprintf("Client connected: %s:%u\n", ipaddr, event.peer->address.port);
+ numplayers++;
+ for (int i = 1; i < kMaxPlayers; i++)
+ {
+ if (gNetPlayerPeer[i] == NULL)
+ {
+ gNetPlayerPeer[i] = event.peer;
+ break;
+ }
+ }
+ if (!bConsole)
+ {
+ char buffer[128];
+ sprintf(buffer, "Waiting for players (%i\\%i)", numplayers, gNetPlayers);
+ viewLoadingScreen(2518, "Network Game", NULL, buffer);
+ }
+ break;
+ }
+ case ENET_EVENT_TYPE_DISCONNECT:
+ {
+ char ipaddr[32];
+
+ enet_address_get_host_ip(&event.peer->address, ipaddr, sizeof(ipaddr));
+ initprintf("Client disconnected: %s:%u\n", ipaddr, event.peer->address.port);
+ numplayers--;
+ for (int i = 1; i < kMaxPlayers; i++)
+ {
+ if (gNetPlayerPeer[i] == event.peer)
+ {
+ gNetPlayerPeer[i] = NULL;
+ for (int j = kMaxPlayers-1; j > i; j--)
+ {
+ if (gNetPlayerPeer[j])
+ {
+ gNetPlayerPeer[i] = gNetPlayerPeer[j];
+ gNetPlayerPeer[j] = NULL;
+ break;
+ }
+ }
+ }
+ }
+ if (!bConsole)
+ {
+ char buffer[128];
+ sprintf(buffer, "Waiting for players (%i\\%i)", numplayers, gNetPlayers);
+ viewLoadingScreen(2518, "Network Game", NULL, buffer);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+ initprintf("All clients connected\n");
+
+ dassert(numplayers >= 1);
+
+ gInitialNetPlayers = gNetPlayers = numplayers;
+ connecthead = 0;
+ for (int i = 0; i < numplayers-1; i++)
+ connectpoint2[i] = i+1;
+ connectpoint2[numplayers-1] = -1;
+
+ enet_host_service(gNetENetServer, NULL, 0);
+
+ // Send connect info
+ char *pPacket = packet;
+ PutPacketByte(pPacket, BLOOD_SERVICE_CONNECTINFO);
+ PKT_CONNECTINFO connectinfo;
+ connectinfo.numplayers = numplayers;
+ connectinfo.connecthead = connecthead;
+ for (int i = 0; i < numplayers; i++)
+ connectinfo.connectpoint2[i] = connectpoint2[i];
+ PutPacketBuffer(pPacket, &connectinfo, sizeof(connectinfo));
+ ENetPacket *pEPacket = enet_packet_create(packet, pPacket-packet, ENET_PACKET_FLAG_RELIABLE);
+ enet_host_broadcast(gNetENetServer, BLOOD_ENET_SERVICE, pEPacket);
+ //enet_packet_destroy(pEPacket);
+
+ enet_host_service(gNetENetServer, NULL, 0);
+
+ // Send id
+ myconnectindex = 0;
+ for (int i = 1; i < numplayers; i++)
+ {
+ if (!gNetPlayerPeer[i])
+ continue;
+ char *pPacket = packet;
+ PutPacketByte(pPacket, BLOOD_SERVICE_CONNECTID);
+ PutPacketByte(pPacket, i);
+ ENetPacket *pEPacket = enet_packet_create(packet, pPacket-packet, ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(gNetPlayerPeer[i], BLOOD_ENET_SERVICE, pEPacket);
+
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+ else if (gNetMode == NETWORK_CLIENT)
+ {
+ ENetEvent event;
+ sprintf(buffer, "Connecting to %s:%u", gNetAddress, gNetPort);
+ initprintf("%s\n", buffer);
+ if (!bConsole)
+ {
+ viewLoadingScreen(2518, "Network Game", NULL, buffer);
+ }
+ gNetENetClient = enet_host_create(NULL, 1, BLOOD_ENET_CHANNEL_MAX, 0, 0);
+ enet_address_set_host(&gNetENetAddress, gNetAddress);
+ gNetENetAddress.port = gNetPort;
+ gNetENetPeer = enet_host_connect(gNetENetClient, &gNetENetAddress, BLOOD_ENET_CHANNEL_MAX, 0);
+ if (!gNetENetPeer)
+ {
+ initprintf("No available peers for initiating an ENet connection.\n");
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ }
+ if (enet_host_service(gNetENetClient, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT)
+ initprintf("Connected to %s:%i\n", gNetAddress, gNetPort);
+ else
+ {
+ initprintf("Could not connect to %s:%i\n", gNetAddress, gNetPort);
+ netDeinitialize();
+ return;
+ }
+ bool bWaitServer = true;
+ if (!bConsole)
+ {
+ viewLoadingScreen(2518, "Network Game", NULL, "Waiting for server response");
+ }
+ while (bWaitServer)
+ {
+ handleevents();
+ if (quitevent)
+ {
+ netClientDisconnect();
+ QuitGame();
+ }
+ if (!bConsole && KB_KeyPressed(sc_Escape))
+ {
+ netClientDisconnect();
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ }
+ enet_host_service(gNetENetClient, NULL, 0);
+ if (enet_host_check_events(gNetENetClient, &event) > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_DISCONNECT:
+ initprintf("Lost connection to server\n");
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ case ENET_EVENT_TYPE_RECEIVE:
+ //initprintf("NETEVENT\n");
+ if (event.channelID == BLOOD_ENET_SERVICE)
+ {
+ char *pPacket = (char*)event.packet->data;
+ switch (GetPacketByte(pPacket))
+ {
+ case BLOOD_SERVICE_CONNECTINFO:
+ {
+ PKT_CONNECTINFO *connectinfo = (PKT_CONNECTINFO*)pPacket;
+ gInitialNetPlayers = gNetPlayers = numplayers = connectinfo->numplayers;
+ connecthead = connectinfo->connecthead;
+ for (int i = 0; i < numplayers; i++)
+ connectpoint2[i] = connectinfo->connectpoint2[i];
+ //initprintf("BLOOD_SERVICE_CONNECTINFO\n");
+ break;
+ }
+ case BLOOD_SERVICE_CONNECTID:
+ dassert(numplayers > 1);
+ myconnectindex = GetPacketByte(pPacket);
+ bWaitServer = false;
+ //initprintf("BLOOD_SERVICE_CONNECTID\n");
+ break;
+ }
+ }
+ enet_packet_destroy(event.packet);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ enet_host_service(gNetENetClient, NULL, 0);
+ initprintf("Successfully connected to server\n");
+ }
+ gNetENetInit = true;
+ gGameOptions.nGameType = 2;
+#endif
+}
+
+void netDeinitialize(void)
+{
+#ifndef NETCODE_DISABLE
+ if (!gNetENetInit)
+ return;
+ gNetENetInit = false;
+ if (gNetMode != NETWORK_NONE)
+ {
+ if (gNetENetServer)
+ {
+ netServerDisconnect();
+ enet_host_destroy(gNetENetServer);
+ }
+ else if (gNetENetClient)
+ {
+ netClientDisconnect();
+ enet_host_destroy(gNetENetClient);
+ }
+ enet_deinitialize();
+ }
+ gNetENetServer = gNetENetClient = NULL;
+#endif
+}
+
+#ifndef NETCODE_DISABLE
+void netPostEPacket(ENetPacket *pEPacket)
+{
+ //static int number;
+ //initprintf("netPostEPacket %i\n", number++);
+ gNetPacketFifo[gNetPacketHead] = pEPacket;
+ gNetPacketHead = (gNetPacketHead+1)%kENetFifoSize;
+}
+#endif
+
+void netUpdate(void)
+{
+#ifndef NETCODE_DISABLE
+ ENetEvent event;
+ if (gNetMode == NETWORK_NONE)
+ return;
+
+ if (gNetENetServer)
+ {
+ enet_host_service(gNetENetServer, NULL, 0);
+ while (enet_host_check_events(gNetENetServer, &event) > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_DISCONNECT:
+ {
+ int nPlayer;
+ for (nPlayer = connectpoint2[connecthead]; nPlayer >= 0; nPlayer = connectpoint2[nPlayer])
+ if (gNetPlayerPeer[nPlayer] == event.peer)
+ break;
+
+ for (int p = 0; p < kMaxPlayers; p++)
+ if (gNetPlayerPeer[p] == event.peer)
+ gNetPlayerPeer[p] = NULL;
+ if (nPlayer >= 0)
+ {
+ // TODO: Game most likely will go out of sync here...
+ char *pPacket = packet;
+ PutPacketByte(pPacket, 249);
+ PutPacketDWord(pPacket, nPlayer);
+ netSendPacketAll(packet, pPacket - packet);
+ netPlayerQuit(nPlayer);
+ netWaitForEveryone(0);
+ }
+ if (gNetPlayers <= 1)
+ {
+ netDeinitialize();
+ netResetToSinglePlayer();
+ return;
+ }
+ break;
+ }
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.channelID)
+ {
+ case BLOOD_ENET_SERVICE:
+ {
+ char *pPacket = (char*)event.packet->data;
+ if (GetPacketByte(pPacket) != BLOOD_SERVICE_SENDTOID)
+ ThrowError("Packet error\n");
+ int nDest = GetPacketByte(pPacket);
+ int nSource = GetPacketByte(pPacket);
+ int nSize = event.packet->dataLength-3;
+ if (gNetPlayerPeer[nDest] != NULL)
+ {
+ ENetPacket *pNetPacket = enet_packet_create(NULL, nSize + 1, ENET_PACKET_FLAG_RELIABLE);
+ char *pPBuffer = (char*)pNetPacket->data;
+ PutPacketByte(pPBuffer, nSource);
+ PutPacketBuffer(pPBuffer, pPacket, nSize);
+ enet_peer_send(gNetPlayerPeer[nDest], BLOOD_ENET_GAME, pNetPacket);
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+ enet_packet_destroy(event.packet);
+ break;
+ }
+ case BLOOD_ENET_GAME:
+ netPostEPacket(event.packet);
+ break;
+ }
+ default:
+ break;
+ }
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+ enet_host_service(gNetENetServer, NULL, 0);
+ }
+ else if (gNetENetClient)
+ {
+ enet_host_service(gNetENetClient, NULL, 0);
+ while (enet_host_check_events(gNetENetClient, &event) > 0)
+ {
+ switch (event.type)
+ {
+ case ENET_EVENT_TYPE_DISCONNECT:
+ initprintf("Lost connection to server\n");
+ netDeinitialize();
+ netResetToSinglePlayer();
+ gQuitGame = gRestartGame = true;
+ return;
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.channelID)
+ {
+ case BLOOD_ENET_SERVICE:
+ {
+ ThrowError("Packet error\n");
+ break;
+ }
+ case BLOOD_ENET_GAME:
+ netPostEPacket(event.packet);
+ break;
+ }
+ default:
+ break;
+ }
+ enet_host_service(gNetENetClient, NULL, 0);
+ }
+ enet_host_service(gNetENetClient, NULL, 0);
+ }
+#endif
+}
+
+void faketimerhandler(void)
+{
+ timerUpdate();
+#ifndef NETCODE_DISABLE
+ if (gNetMode != NETWORK_NONE && gNetENetInit)
+ netUpdate();
+#if 0
+ if (gGameClock >= gNetFifoClock && ready2send)
+ {
+ gNetFifoClock += 4;
+ netGetInput();
+ }
+#endif
+#endif
+ //if (gNetMode != NETWORK_NONE && gNetENetInit)
+ // enet_host_service(gNetMode == NETWORK_SERVER ? gNetENetServer : gNetENetClient, NULL, 0);
+}
+
+void netPlayerQuit(int nPlayer)
+{
+ char buffer[128];
+ sprintf(buffer, "%s left the game with %d frags.", gProfile[nPlayer].name, gPlayer[nPlayer].at2c6);
+ viewSetMessage(buffer);
+ if (gGameStarted)
+ {
+ seqKill(3, gPlayer[nPlayer].pSprite->extra);
+ actPostSprite(gPlayer[nPlayer].at5b, kStatFree);
+ if (nPlayer == gViewIndex)
+ gViewIndex = myconnectindex;
+ gView = &gPlayer[gViewIndex];
+ }
+ if (nPlayer == connecthead)
+ {
+ connecthead = connectpoint2[connecthead];
+ //if (gPacketMode == PACKETMODE_1)
+ {
+ //byte_27B2CC = 1;
+ gQuitGame = true;
+ gRestartGame = true;
+ gNetPlayers = 1;
+ //gQuitRequest = 1;
+ }
+ }
+ else
+ {
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (connectpoint2[p] == nPlayer)
+ connectpoint2[p] = connectpoint2[nPlayer];
+ }
+#ifndef NETCODE_DISABLE
+ gNetPlayerPeer[nPlayer] = NULL;
+#endif
+ }
+ gNetPlayers--;
+ numplayers = ClipLow(numplayers-1, 1);
+ if (gNetPlayers <= 1)
+ {
+ netDeinitialize();
+ netResetToSinglePlayer();
+ }
+}
diff --git a/source/blood/src/network.h b/source/blood/src/network.h
new file mode 100644
index 000000000..a6c8f34b1
--- /dev/null
+++ b/source/blood/src/network.h
@@ -0,0 +1,153 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "controls.h"
+
+enum PACKETMODE {
+ PACKETMODE_0 = 0,
+ PACKETMODE_1,
+ PACKETMODE_2,
+ PACKETMODE_3,
+};
+
+enum NETWORKMODE {
+ NETWORK_NONE = 0,
+ NETWORK_SERVER,
+ NETWORK_CLIENT
+};
+
+#define kNetDefaultPort 23513
+
+extern char packet[576];
+extern bool gStartNewGame;
+extern PACKETMODE gPacketMode;
+extern int gNetFifoClock;
+extern int gNetFifoTail;
+extern int gNetFifoHead[8];
+extern int gPredictTail;
+extern int gNetFifoMasterTail;
+extern GINPUT gFifoInput[256][8];
+extern int myMinLag[8];
+extern int otherMinLag;
+extern int myMaxLag;
+extern unsigned int gChecksum[4];
+extern unsigned int gCheckFifo[256][8][4];
+extern int gCheckHead[8];
+extern int gSendCheckTail;
+extern int gCheckTail;
+extern int gInitialNetPlayers;
+extern int gBufferJitter;
+extern int gPlayerReady[8];
+extern int gSyncRate;
+extern bool bNoResend;
+extern bool gRobust;
+extern bool bOutOfSync;
+extern bool ready2send;
+extern NETWORKMODE gNetMode;
+extern char gNetAddress[32];
+extern int gNetPort;
+
+
+struct PKT_STARTGAME {
+ short version;
+ char gameType, difficulty, monsterSettings, weaponSettings, itemSettings, respawnSettings;
+ char episodeId, levelId;
+ int unk;
+ char userMap, userMapName[13];
+};
+
+extern PKT_STARTGAME gPacketStartGame;
+
+
+inline void PutPacketByte(char *&p, int a2)
+{
+ //dassert(p - packet + 1 < sizeof(packet));
+ *p++ = a2;
+}
+
+inline void PutPacketWord(char *&p, int a2)
+{
+ //dassert(p - packet + 2 < sizeof(packet));
+ *(short*)p = a2;
+ p += 2;
+}
+
+inline void PutPacketDWord(char *&p, int a2)
+{
+ //dassert(p - packet + 4 < sizeof(packet));
+ *(int*)p = a2;
+ p += 4;
+}
+
+inline void PutPacketBuffer(char *&p, const void *pBuffer, int size)
+{
+ //dassert(p + size < packet + sizeof(packet));
+ memcpy(p, pBuffer, size);
+ p += size;
+}
+
+inline char GetPacketByte(char *&p)
+{
+ return *p++;
+}
+
+inline short GetPacketWord(char *&p)
+{
+ short t = *(short*)p;
+ p += 2;
+ return t;
+}
+
+inline int GetPacketDWord(char *&p)
+{
+ int t = *(int*)p;
+ p += 4;
+ return t;
+}
+
+inline void GetPacketBuffer(char *&p, void *pBuffer, int size)
+{
+ //dassert(p + size < packet + sizeof(packet));
+ memcpy(pBuffer, p, size);
+ p += size;
+}
+
+void sub_79760(void);
+void netResetToSinglePlayer(void);
+void netBroadcastMessage(int nPlayer, const char *pzMessage);
+void netWaitForEveryone(char a1);
+void sub_7AC28(const char *pzString);
+void netGetPackets(void);
+void netBroadcastTaunt(int nPlayer, int nTaunt);
+void CalcGameChecksum(void);
+void netBroadcastPlayerLogoff(int nPlayer);
+void netBroadcastMyLogoff(bool bRestart);
+void netInitialize(bool bConsole);
+void netBroadcastPlayerInfo(int nPlayer);
+void netCheckSync(void);
+void netMasterUpdate(void);
+void netGetInput(void);
+void netPlayerQuit(int nPlayer);
+void netUpdate(void);
+void netDeinitialize(void);
+void netBroadcastNewGame(void);
diff --git a/source/blood/src/osdcmd.cpp b/source/blood/src/osdcmd.cpp
new file mode 100644
index 000000000..b98d0fc57
--- /dev/null
+++ b/source/blood/src/osdcmd.cpp
@@ -0,0 +1,1161 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include "build.h"
+#include "baselayer.h"
+#include "keyboard.h"
+#include "control.h"
+#include "osd.h"
+#include "compat.h"
+#include "mmulti.h"
+#include "common_game.h"
+#include "config.h"
+#include "blood.h"
+#include "demo.h"
+#include "gamemenu.h"
+#include "levels.h"
+#include "messages.h"
+#include "network.h"
+#include "osdcmds.h"
+#include "screen.h"
+#include "sound.h"
+#include "sfx.h"
+#include "view.h"
+
+
+static inline int osdcmd_quit(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+ OSD_ShowDisplay(0);
+ QuitGame();
+ return OSDCMD_OK;
+}
+
+static int osdcmd_changelevel(osdcmdptr_t parm)
+{
+ int32_t volume,level;
+ char *p;
+
+ if (parm->numparms != 2) return OSDCMD_SHOWHELP;
+
+ volume = strtol(parm->parms[0], &p, 10) - 1;
+ if (p[0]) return OSDCMD_SHOWHELP;
+ level = strtol(parm->parms[1], &p, 10) - 1;
+ if (p[0]) return OSDCMD_SHOWHELP;
+
+ if (volume < 0) return OSDCMD_SHOWHELP;
+ if (level < 0) return OSDCMD_SHOWHELP;
+
+ if (volume >= 6)
+ {
+ OSD_Printf("changelevel: invalid volume number (range 1-%d)\n",6);
+ return OSDCMD_OK;
+ }
+
+ if (level >= gEpisodeInfo[volume].nLevels)
+ {
+ OSD_Printf("changelevel: invalid level number\n");
+ return OSDCMD_SHOWHELP;
+ }
+
+ if (gDemo.at1)
+ gDemo.StopPlayback();
+
+ if (numplayers > 1)
+ {
+ gPacketStartGame.episodeId = volume;
+ gPacketStartGame.levelId = level;
+ netBroadcastNewGame();
+ gStartNewGame = 1;
+ gGameMenuMgr.Deactivate();
+ return OSDCMD_OK;
+ }
+ levelSetupOptions(volume, level);
+ StartLevel(&gGameOptions);
+ viewResizeView(gViewSize);
+ gGameMenuMgr.Deactivate();
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_map(osdcmdptr_t parm)
+{
+ char filename[BMAX_PATH];
+
+ const int32_t wildcardp = parm->numparms==1 &&
+ (Bstrchr(parm->parms[0], '*') != NULL);
+
+ if (parm->numparms != 1 || wildcardp)
+ {
+ CACHE1D_FIND_REC *r;
+ fnlist_t fnlist = FNLIST_INITIALIZER;
+ int32_t maxwidth = 0;
+
+ if (wildcardp)
+ maybe_append_ext(filename, sizeof(filename), parm->parms[0], ".map");
+ else
+ Bstrcpy(filename, "*.MAP");
+
+ fnlist_getnames(&fnlist, "/", filename, -1, 0);
+ gSysRes.FNAddFiles(&fnlist, filename);
+
+ for (r=fnlist.findfiles; r; r=r->next)
+ maxwidth = max(maxwidth, Bstrlen(r->name));
+
+ if (maxwidth > 0)
+ {
+ int32_t x = 0;
+ maxwidth += 3;
+ OSD_Printf(OSDTEXT_RED "Map listing:\n");
+ for (r=fnlist.findfiles; r; r=r->next)
+ {
+ OSD_Printf("%-*s",maxwidth,r->name);
+ x += maxwidth;
+ if (x > OSD_GetCols() - maxwidth)
+ {
+ x = 0;
+ OSD_Printf("\n");
+ }
+ }
+ if (x) OSD_Printf("\n");
+ OSD_Printf(OSDTEXT_RED "Found %d maps\n", fnlist.numfiles);
+ }
+
+ fnlist_clearnames(&fnlist);
+
+ return OSDCMD_SHOWHELP;
+ }
+
+ strcpy(filename, parm->parms[0]);
+ ChangeExtension(filename, "");
+
+ if (!gSysRes.Lookup(filename, "MAP"))
+ {
+ OSD_Printf(OSD_ERROR "map: file \"%s\" not found.\n", filename);
+ return OSDCMD_OK;
+ }
+
+ if (gDemo.at1)
+ gDemo.StopPlayback();
+
+ levelAddUserMap(filename);
+
+ if (numplayers > 1)
+ {
+ gPacketStartGame.episodeId = gGameOptions.nEpisode;
+ gPacketStartGame.levelId = gGameOptions.nLevel;
+ netBroadcastNewGame();
+ gStartNewGame = 1;
+ gGameMenuMgr.Deactivate();
+ return OSDCMD_OK;
+ }
+ levelSetupOptions(gGameOptions.nEpisode, gGameOptions.nLevel);
+ StartLevel(&gGameOptions);
+ viewResizeView(gViewSize);
+ gGameMenuMgr.Deactivate();
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_demo(osdcmdptr_t parm)
+{
+ if (numplayers > 1)
+ {
+ OSD_Printf("Command not allowed in multiplayer\n");
+ return OSDCMD_OK;
+ }
+
+ //if (g_player[myconnectindex].ps->gm & MODE_GAME)
+ //{
+ // OSD_Printf("demo: Must not be in a game.\n");
+ // return OSDCMD_OK;
+ //}
+
+ if (parm->numparms != 1/* && parm->numparms != 2*/)
+ return OSDCMD_SHOWHELP;
+
+ gDemo.SetupPlayback(parm->parms[0]);
+ gGameStarted = 0;
+ gDemo.Playback();
+
+ return OSDCMD_OK;
+}
+
+int osdcmd_restartvid(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+ videoResetMode();
+ if (videoSetGameMode(gSetup.fullscreen,gSetup.xdim,gSetup.ydim,gSetup.bpp,0))
+ ThrowError("restartvid: Reset failed...\n");
+ onvideomodechange(gSetup.bpp>8);
+ viewResizeView(gViewSize);
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_music(osdcmdptr_t parm)
+{
+ char buffer[128];
+ if (parm->numparms == 1)
+ {
+ int32_t sel = levelGetMusicIdx(parm->parms[0]);
+
+ if (sel == -1)
+ return OSDCMD_SHOWHELP;
+
+ if (sel == -2)
+ {
+ OSD_Printf("%s is not a valid episode/level number pair\n", parm->parms[0]);
+ return OSDCMD_OK;
+ }
+
+ int nEpisode = sel/kMaxLevels;
+ int nLevel = sel%kMaxLevels;
+
+ if (!levelTryPlayMusic(nEpisode, nLevel))
+ {
+ if (CDAudioToggle)
+ snprintf(buffer, sizeof(buffer), "Playing %i track", gEpisodeInfo[nEpisode].at28[nLevel].ate0);
+ else
+ snprintf(buffer, sizeof(buffer), "Playing %s", gEpisodeInfo[nEpisode].at28[nLevel].atd0);
+ viewSetMessage(buffer);
+ }
+ else
+ {
+ OSD_Printf("No music defined for %s\n", parm->parms[0]);
+ }
+
+ return OSDCMD_OK;
+ }
+
+ return OSDCMD_SHOWHELP;
+}
+
+static int osdcmd_vidmode(osdcmdptr_t parm)
+{
+ int32_t newbpp = gSetup.bpp, newwidth = gSetup.xdim,
+ newheight = gSetup.ydim, newfs = gSetup.fullscreen;
+ int32_t tmp;
+
+ if (parm->numparms < 1 || parm->numparms > 4) return OSDCMD_SHOWHELP;
+
+ switch (parm->numparms)
+ {
+ case 1: // bpp switch
+ tmp = Batol(parm->parms[0]);
+ if (!(tmp==8 || tmp==16 || tmp==32))
+ return OSDCMD_SHOWHELP;
+ newbpp = tmp;
+ break;
+ case 2: // res switch
+ newwidth = Batol(parm->parms[0]);
+ newheight = Batol(parm->parms[1]);
+ break;
+ case 3: // res & bpp switch
+ case 4:
+ newwidth = Batol(parm->parms[0]);
+ newheight = Batol(parm->parms[1]);
+ tmp = Batol(parm->parms[2]);
+ if (!(tmp==8 || tmp==16 || tmp==32))
+ return OSDCMD_SHOWHELP;
+ newbpp = tmp;
+ if (parm->numparms == 4)
+ newfs = (Batol(parm->parms[3]) != 0);
+ break;
+ }
+
+ if (videoSetGameMode(newfs,newwidth,newheight,newbpp,upscalefactor))
+ {
+ initprintf("vidmode: Mode change failed!\n");
+ if (videoSetGameMode(gSetup.fullscreen, gSetup.xdim, gSetup.ydim, gSetup.bpp, upscalefactor))
+ ThrowError("vidmode: Reset failed!\n");
+ }
+ gSetup.bpp = newbpp;
+ gSetup.xdim = newwidth;
+ gSetup.ydim = newheight;
+ gSetup.fullscreen = newfs;
+ onvideomodechange(gSetup.bpp>8);
+ viewResizeView(gViewSize);
+ return OSDCMD_OK;
+}
+
+static int osdcmd_crosshaircolor(osdcmdptr_t parm)
+{
+ if (parm->numparms != 3)
+ {
+ OSD_Printf("crosshaircolor: r:%d g:%d b:%d\n",CrosshairColors.r,CrosshairColors.g,CrosshairColors.b);
+ return OSDCMD_SHOWHELP;
+ }
+
+ uint8_t const r = Batol(parm->parms[0]);
+ uint8_t const g = Batol(parm->parms[1]);
+ uint8_t const b = Batol(parm->parms[2]);
+
+ g_isAlterDefaultCrosshair = true;
+ viewSetCrosshairColor(r,g,b);
+
+ if (!OSD_ParsingScript())
+ OSD_Printf("%s\n", parm->raw);
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_resetcrosshair(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+ g_isAlterDefaultCrosshair = false;
+ viewResetCrosshairToDefault();
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_give(osdcmdptr_t parm)
+{
+ if (numplayers != 1 || !gGameStarted || gMe->pXSprite->health == 0)
+ {
+ OSD_Printf("give: Cannot give while dead or not in a single-player game.\n");
+ return OSDCMD_OK;
+ }
+
+ if (parm->numparms != 1) return OSDCMD_SHOWHELP;
+
+ if (!Bstrcasecmp(parm->parms[0], "all"))
+ {
+ SetWeapons(true);
+ SetAmmo(true);
+ SetToys(true);
+ SetArmor(true);
+ SetKeys(true);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ else if (!Bstrcasecmp(parm->parms[0], "health"))
+ {
+ actHealDude(gMe->pXSprite, 200, 200);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ else if (!Bstrcasecmp(parm->parms[0], "weapons"))
+ {
+ SetWeapons(true);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ else if (!Bstrcasecmp(parm->parms[0], "ammo"))
+ {
+ SetAmmo(true);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ else if (!Bstrcasecmp(parm->parms[0], "armor"))
+ {
+ SetArmor(true);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ else if (!Bstrcasecmp(parm->parms[0], "keys"))
+ {
+ SetKeys(true);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ else if (!Bstrcasecmp(parm->parms[0], "inventory"))
+ {
+ SetToys(true);
+ gCheatMgr.m_bPlayerCheated = true;
+ return OSDCMD_OK;
+ }
+ return OSDCMD_SHOWHELP;
+}
+
+static int osdcmd_god(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+ if (numplayers == 1 && gGameStarted)
+ {
+ SetGodMode(!gMe->at31a);
+ gCheatMgr.m_bPlayerCheated = true;
+ }
+ else
+ OSD_Printf("god: Not in a single-player game.\n");
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_noclip(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+
+ if (numplayers == 1 && gGameStarted)
+ {
+ SetClipMode(!gNoClip);
+ gCheatMgr.m_bPlayerCheated = true;
+ }
+ else
+ {
+ OSD_Printf("noclip: Not in a single-player game.\n");
+ }
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_restartsound(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+ sfxTerm();
+ sndTerm();
+
+ sndInit();
+ sfxInit();
+
+ if (MusicToggle && (gGameStarted || gDemo.at1))
+ sndPlaySong(gGameOptions.zLevelSong, true);
+
+ return OSDCMD_OK;
+}
+
+void onvideomodechange(int32_t newmode)
+{
+ UNREFERENCED_PARAMETER(newmode);
+#if 0
+ uint8_t palid;
+
+ // XXX?
+ if (!newmode || g_player[screenpeek].ps->palette < BASEPALCOUNT)
+ palid = g_player[screenpeek].ps->palette;
+ else
+ palid = BASEPAL;
+
+#ifdef POLYMER
+ if (videoGetRenderMode() == REND_POLYMER)
+ {
+ int32_t i = 0;
+
+ while (i < MAXSPRITES)
+ {
+ if (actor[i].lightptr)
+ {
+ polymer_deletelight(actor[i].lightId);
+ actor[i].lightptr = NULL;
+ actor[i].lightId = -1;
+ }
+ i++;
+ }
+ }
+#endif
+
+ videoSetPalette(ud.brightness>>2, palid, 0);
+ g_restorePalette = -1;
+#endif
+ if (newmode)
+ scrResetPalette();
+ UpdateDacs(gLastPal, false);
+}
+
+static int osdcmd_button(osdcmdptr_t parm)
+{
+ static char const s_gamefunc_[] = "gamefunc_";
+ int constexpr strlen_gamefunc_ = ARRAY_SIZE(s_gamefunc_) - 1;
+
+ char const *p = parm->name + strlen_gamefunc_;
+
+// if (g_player[myconnectindex].ps->gm == MODE_GAME) // only trigger these if in game
+ if (gInputMode == INPUT_MODE_0)
+ CONTROL_ButtonFlags[CONFIG_FunctionNameToNum(p)] = 1; // FIXME
+
+ return OSDCMD_OK;
+}
+
+const char *const ConsoleButtons[] =
+{
+ "mouse1", "mouse2", "mouse3", "mouse4", "mwheelup",
+ "mwheeldn", "mouse5", "mouse6", "mouse7", "mouse8"
+};
+
+static int osdcmd_bind(osdcmdptr_t parm)
+{
+ char buffer[256];
+ if (parm->numparms==1 && !Bstrcasecmp(parm->parms[0],"showkeys"))
+ {
+ for (int i=0; sctokeylut[i].key; i++)
+ OSD_Printf("%s\n",sctokeylut[i].key);
+ for (auto ConsoleButton : ConsoleButtons)
+ OSD_Printf("%s\n",ConsoleButton);
+ return OSDCMD_OK;
+ }
+
+ if (parm->numparms==0)
+ {
+ int j=0;
+
+ OSD_Printf("Current key bindings:\n");
+
+ for (int i=0; iparms[0], sctokeylut[i].key))
+ break;
+ }
+
+ // didn't find the key
+ if (i == ARRAY_SSIZE(sctokeylut))
+ {
+ for (i=0; iparms[0],ConsoleButtons[i]))
+ break;
+
+ if (i >= MAXMOUSEBUTTONS)
+ return OSDCMD_SHOWHELP;
+
+ if (parm->numparms < 2)
+ {
+ if (CONTROL_KeyBinds[MAXBOUNDKEYS + i].cmdstr && CONTROL_KeyBinds[MAXBOUNDKEYS + i ].key)
+ OSD_Printf("%-9s %s\"%s\"\n", ConsoleButtons[i], CONTROL_KeyBinds[MAXBOUNDKEYS + i].repeat?"":"norepeat ",
+ CONTROL_KeyBinds[MAXBOUNDKEYS + i].cmdstr);
+ else OSD_Printf("%s is unbound\n", ConsoleButtons[i]);
+ return OSDCMD_OK;
+ }
+
+ j = 1;
+
+ repeat = 1;
+ if (!Bstrcasecmp(parm->parms[j],"norepeat"))
+ {
+ repeat = 0;
+ j++;
+ }
+
+ Bstrcpy(buffer,parm->parms[j++]);
+ for (; jnumparms; j++)
+ {
+ Bstrcat(buffer," ");
+ Bstrcat(buffer,parm->parms[j++]);
+ }
+
+ CONTROL_BindMouse(i, buffer, repeat, ConsoleButtons[i]);
+
+ if (!OSD_ParsingScript())
+ OSD_Printf("%s\n",parm->raw);
+ return OSDCMD_OK;
+ }
+
+ if (parm->numparms < 2)
+ {
+ if (CONTROL_KeyIsBound(sctokeylut[i].sc))
+ OSD_Printf("%-9s %s\"%s\"\n", sctokeylut[i].key, CONTROL_KeyBinds[sctokeylut[i].sc].repeat?"":"norepeat ",
+ CONTROL_KeyBinds[sctokeylut[i].sc].cmdstr);
+ else OSD_Printf("%s is unbound\n", sctokeylut[i].key);
+
+ return OSDCMD_OK;
+ }
+
+ j = 1;
+
+ repeat = 1;
+ if (!Bstrcasecmp(parm->parms[j],"norepeat"))
+ {
+ repeat = 0;
+ j++;
+ }
+
+ Bstrcpy(buffer,parm->parms[j++]);
+ for (; jnumparms; j++)
+ {
+ Bstrcat(buffer," ");
+ Bstrcat(buffer,parm->parms[j++]);
+ }
+
+ CONTROL_BindKey(sctokeylut[i].sc, buffer, repeat, sctokeylut[i].key);
+
+ char *cp = buffer;
+
+ // Populate the keyboard config menu based on the bind.
+ // Take care of processing one-to-many bindings properly, too.
+ static char const s_gamefunc_[] = "gamefunc_";
+ int constexpr strlen_gamefunc_ = ARRAY_SIZE(s_gamefunc_) - 1;
+
+ while ((cp = Bstrstr(cp, s_gamefunc_)))
+ {
+ cp += strlen_gamefunc_;
+
+ char *semi = Bstrchr(cp, ';');
+
+ if (semi)
+ *semi = 0;
+
+ j = CONFIG_FunctionNameToNum(cp);
+
+ if (semi)
+ cp = semi+1;
+
+ if (j != -1)
+ {
+ KeyboardKeys[j][1] = KeyboardKeys[j][0];
+ KeyboardKeys[j][0] = sctokeylut[i].sc;
+// CONTROL_MapKey(j, sctokeylut[i].sc, ud.config.KeyboardKeys[j][0]);
+
+ if (j == gamefunc_Show_Console)
+ OSD_CaptureKey(sctokeylut[i].sc);
+ }
+ }
+
+ if (!OSD_ParsingScript())
+ OSD_Printf("%s\n",parm->raw);
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_unbindall(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_CONST_PARAMETER(parm);
+
+ for (int i = 0; i < MAXBOUNDKEYS; ++i)
+ CONTROL_FreeKeyBind(i);
+
+ for (int i = 0; i < MAXMOUSEBUTTONS; ++i)
+ CONTROL_FreeMouseBind(i);
+
+ for (auto &KeyboardKey : KeyboardKeys)
+ KeyboardKey[0] = KeyboardKey[1] = 0xff;
+
+ if (!OSD_ParsingScript())
+ OSD_Printf("unbound all controls\n");
+
+ return OSDCMD_OK;
+}
+
+static int osdcmd_unbind(osdcmdptr_t parm)
+{
+ if (parm->numparms != 1)
+ return OSDCMD_SHOWHELP;
+
+ for (auto ConsoleKey : sctokeylut)
+ {
+ if (ConsoleKey.key && !Bstrcasecmp(parm->parms[0], ConsoleKey.key))
+ {
+ CONTROL_FreeKeyBind(ConsoleKey.sc);
+ OSD_Printf("unbound key %s\n", ConsoleKey.key);
+ return OSDCMD_OK;
+ }
+ }
+
+ for (int i = 0; i < MAXMOUSEBUTTONS; i++)
+ {
+ if (!Bstrcasecmp(parm->parms[0], ConsoleButtons[i]))
+ {
+ CONTROL_FreeMouseBind(i);
+ OSD_Printf("unbound %s\n", ConsoleButtons[i]);
+ return OSDCMD_OK;
+ }
+ }
+
+ return OSDCMD_SHOWHELP;
+}
+
+static int osdcmd_screenshot(osdcmdptr_t parm)
+{
+ static const char *fn = "blud0000.png";
+
+ if (parm->numparms == 1 && !Bstrcasecmp(parm->parms[0], "tga"))
+ videoCaptureScreenTGA(fn, 0);
+ else videoCaptureScreen(fn, 0);
+
+ return OSDCMD_OK;
+}
+
+#if 0
+static int osdcmd_savestate(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_PARAMETER(parm);
+ G_SaveMapState();
+ return OSDCMD_OK;
+}
+
+static int osdcmd_restorestate(osdcmdptr_t UNUSED(parm))
+{
+ UNREFERENCED_PARAMETER(parm);
+ G_RestoreMapState();
+ return OSDCMD_OK;
+}
+#endif
+
+#if 0
+#ifdef DEBUGGINGAIDS
+static int osdcmd_inittimer(osdcmdptr_t parm)
+{
+ if (parm->numparms != 1)
+ {
+ OSD_Printf("%dHz timer\n",g_timerTicsPerSecond);
+ return OSDCMD_SHOWHELP;
+ }
+
+ G_InitTimer(Batol(parm->parms[0]));
+
+ OSD_Printf("%s\n",parm->raw);
+ return OSDCMD_OK;
+}
+#endif
+#endif
+
+static int osdcmd_cvar_set_game(osdcmdptr_t parm)
+{
+ int const r = osdcmd_cvar_set(parm);
+
+ if (r != OSDCMD_OK) return r;
+
+ if (!Bstrcasecmp(parm->name, "r_upscalefactor"))
+ {
+ if (in3dmode())
+ {
+ videoSetGameMode(fullscreen, xres, yres, bpp, gUpscaleFactor);
+ }
+ }
+ else if (!Bstrcasecmp(parm->name, "r_size"))
+ {
+ //ud.statusbarmode = (ud.screen_size < 8);
+ viewResizeView(gViewSize);
+ }
+ else if (!Bstrcasecmp(parm->name, "r_maxfps") || !Bstrcasecmp(parm->name, "r_maxfpsoffset"))
+ {
+ if (r_maxfps != 0) r_maxfps = clamp(r_maxfps, 30, 1000);
+ g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset);
+ }
+ else if (!Bstrcasecmp(parm->name, "r_ambientlight"))
+ {
+ if (r_ambientlight == 0)
+ r_ambientlightrecip = 256.f;
+ else r_ambientlightrecip = 1.f/r_ambientlight;
+ }
+ else if (!Bstrcasecmp(parm->name, "in_mouse"))
+ {
+ CONTROL_MouseEnabled = (gSetup.usemouse && CONTROL_MousePresent);
+ }
+ else if (!Bstrcasecmp(parm->name, "in_joystick"))
+ {
+ CONTROL_JoystickEnabled = (gSetup.usejoystick && CONTROL_JoyPresent);
+ }
+ else if (!Bstrcasecmp(parm->name, "vid_gamma"))
+ {
+ gBrightness = GAMMA_CALC;
+ gBrightness <<= 2;
+ videoSetPalette(gBrightness>>2,gLastPal,0);
+ }
+ else if (!Bstrcasecmp(parm->name, "vid_brightness") || !Bstrcasecmp(parm->name, "vid_contrast"))
+ {
+ videoSetPalette(gBrightness>>2,gLastPal,0);
+ }
+#if 0
+ else if (!Bstrcasecmp(parm->name, "hud_scale")
+ || !Bstrcasecmp(parm->name, "hud_statusbarmode")
+ || !Bstrcasecmp(parm->name, "r_rotatespritenowidescreen"))
+ {
+ G_UpdateScreenArea();
+ }
+ else if (!Bstrcasecmp(parm->name, "skill"))
+ {
+ if (numplayers > 1)
+ return r;
+
+ ud.player_skill = ud.m_player_skill;
+ }
+ else if (!Bstrcasecmp(parm->name, "color"))
+ {
+ ud.color = G_CheckPlayerColor(ud.color);
+ g_player[0].ps->palookup = g_player[0].pcolor = ud.color;
+ }
+ else if (!Bstrcasecmp(parm->name, "osdscale"))
+ {
+ osdrscale = 1.f/osdscale;
+
+ if (xdim && ydim)
+ OSD_ResizeDisplay(xdim, ydim);
+ }
+ else if (!Bstrcasecmp(parm->name, "wchoice"))
+ {
+ if (parm->numparms == 1)
+ {
+ if (g_forceWeaponChoice) // rewrite ud.wchoice because osdcmd_cvar_set already changed it
+ {
+ int j = 0;
+
+ while (j < 10)
+ {
+ ud.wchoice[j] = g_player[myconnectindex].wchoice[j] + '0';
+ j++;
+ }
+
+ ud.wchoice[j] = 0;
+ }
+ else
+ {
+ char const *c = parm->parms[0];
+
+ if (*c)
+ {
+ int j = 0;
+
+ while (*c && j < 10)
+ {
+ g_player[myconnectindex].wchoice[j] = *c - '0';
+ c++;
+ j++;
+ }
+
+ while (j < 10)
+ {
+ if (j == 9)
+ g_player[myconnectindex].wchoice[9] = 1;
+ else
+ g_player[myconnectindex].wchoice[j] = 2;
+
+ j++;
+ }
+ }
+ }
+
+ g_forceWeaponChoice = 0;
+ }
+
+ /* Net_SendClientInfo();*/
+ }
+#endif
+
+ return r;
+}
+
+static int osdcmd_cvar_set_multi(osdcmdptr_t parm)
+{
+ int const r = osdcmd_cvar_set_game(parm);
+
+ if (r != OSDCMD_OK) return r;
+
+ //G_UpdatePlayerFromMenu();
+
+ return r;
+}
+
+int32_t registerosdcommands(void)
+{
+ char buffer[256];
+ static osdcvardata_t cvars_game[] =
+ {
+ { "crosshair", "enable/disable crosshair", (void *)&gAimReticle, CVAR_BOOL, 0, 1 },
+
+ { "cl_autoaim", "enable/disable weapon autoaim", (void *)&gAutoAim, CVAR_INT|CVAR_MULTI, 0, 2 },
+// { "cl_automsg", "enable/disable automatically sending messages to all players", (void *)&ud.automsg, CVAR_BOOL, 0, 1 },
+ { "cl_autorun", "enable/disable autorun", (void *)&gAutoRun, CVAR_BOOL, 0, 1 },
+//
+// { "cl_autosave", "enable/disable autosaves", (void *) &ud.autosave, CVAR_BOOL, 0, 1 },
+// { "cl_autosavedeletion", "enable/disable automatic deletion of autosaves", (void *) &ud.autosavedeletion, CVAR_BOOL, 0, 1 },
+// { "cl_maxautosaves", "number of autosaves to keep before deleting the oldest", (void *) &ud.maxautosaves, CVAR_INT, 1, 100 },
+//
+// { "cl_autovote", "enable/disable automatic voting", (void *)&ud.autovote, CVAR_INT, 0, 2 },
+//
+// { "cl_cheatmask", "configure what cheats show in the cheats menu", (void *)&cl_cheatmask, CVAR_UINT, 0, ~0 },
+//
+// { "cl_obituaries", "enable/disable multiplayer death messages", (void *)&ud.obituaries, CVAR_BOOL, 0, 1 },
+// { "cl_democams", "enable/disable demo playback cameras", (void *)&ud.democams, CVAR_BOOL, 0, 1 },
+//
+// { "cl_idplayers", "enable/disable name display when aiming at opponents", (void *)&ud.idplayers, CVAR_BOOL, 0, 1 },
+//
+
+ { "cl_interpolate", "enable/disable view interpolation", (void *)&gViewInterpolate, CVAR_BOOL, 0, 1 },
+ { "cl_viewhbob", "enable/disable view horizontal bobbing", (void *)&gViewHBobbing, CVAR_BOOL, 0, 1 },
+ { "cl_viewvbob", "enable/disable view vertical bobbing", (void *)&gViewVBobbing, CVAR_BOOL, 0, 1 },
+ { "cl_slopetilting", "enable/disable slope tilting", (void *)&gSlopeTilting, CVAR_BOOL, 0, 1 },
+ { "cl_showweapon", "enable/disable show weapons", (void *)&gShowWeapon, CVAR_BOOL, 0, 1 },
+
+ { "cl_runmode", "enable/disable modernized run key operation", (void *)&gRunKeyMode, CVAR_BOOL, 0, 1 },
+//
+// { "cl_showcoords", "show your position in the game world", (void *)&ud.coords, CVAR_INT, 0,
+//#ifdef USE_OPENGL
+// 2
+//#else
+// 1
+//#endif
+// },
+//
+// { "cl_viewbob", "enable/disable player head bobbing", (void *)&ud.viewbob, CVAR_BOOL, 0, 1 },
+//
+// { "cl_weaponsway", "enable/disable player weapon swaying", (void *)&ud.weaponsway, CVAR_BOOL, 0, 1 },
+ { "cl_weaponswitch", "enable/disable auto weapon switching", (void *)&gWeaponSwitch, CVAR_INT|CVAR_MULTI, 0, 3 },
+//
+// { "color", "changes player palette", (void *)&ud.color, CVAR_INT|CVAR_MULTI, 0, MAXPALOOKUPS-1 },
+//
+// { "crosshairscale","changes the size of the crosshair", (void *)&ud.crosshairscale, CVAR_INT, 10, 100 },
+//
+// { "demorec_diffs","enable/disable diff recording in demos",(void *)&demorec_diffs_cvar, CVAR_BOOL, 0, 1 },
+// { "demorec_force","enable/disable forced demo recording",(void *)&demorec_force_cvar, CVAR_BOOL|CVAR_NOSAVE, 0, 1 },
+// {
+// "demorec_difftics","sets game tic interval after which a diff is recorded",
+// (void *)&demorec_difftics_cvar, CVAR_INT, 2, 60*REALGAMETICSPERSEC
+// },
+// { "demorec_diffcompress","Compression method for diffs. (0: none, 1: KSLZW)",(void *)&demorec_diffcompress_cvar, CVAR_INT, 0, 1 },
+// { "demorec_synccompress","Compression method for input. (0: none, 1: KSLZW)",(void *)&demorec_synccompress_cvar, CVAR_INT, 0, 1 },
+// { "demorec_seeds","enable/disable recording of random seed for later sync checking",(void *)&demorec_seeds_cvar, CVAR_BOOL, 0, 1 },
+// { "demoplay_diffs","enable/disable application of diffs in demo playback",(void *)&demoplay_diffs, CVAR_BOOL, 0, 1 },
+// { "demoplay_showsync","enable/disable display of sync status",(void *)&demoplay_showsync, CVAR_BOOL, 0, 1 },
+//
+// { "hud_althud", "enable/disable alternate mini-hud", (void *)&ud.althud, CVAR_BOOL, 0, 1 },
+// { "hud_custom", "change the custom hud", (void *)&ud.statusbarcustom, CVAR_INT, 0, ud.statusbarrange },
+// { "hud_position", "aligns the status bar to the bottom/top", (void *)&ud.hudontop, CVAR_BOOL, 0, 1 },
+// { "hud_bgstretch", "enable/disable background image stretching in wide resolutions", (void *)&ud.bgstretch, CVAR_BOOL, 0, 1 },
+// { "hud_messagetime", "length of time to display multiplayer chat messages", (void *)&ud.msgdisptime, CVAR_INT, 0, 3600 },
+// { "hud_numbertile", "first tile in alt hud number set", (void *)&althud_numbertile, CVAR_INT, 0, MAXUSERTILES-10 },
+// { "hud_numberpal", "pal for alt hud numbers", (void *)&althud_numberpal, CVAR_INT, 0, MAXPALOOKUPS-1 },
+// { "hud_shadows", "enable/disable althud shadows", (void *)&althud_shadows, CVAR_BOOL, 0, 1 },
+// { "hud_flashing", "enable/disable althud flashing", (void *)&althud_flashing, CVAR_BOOL, 0, 1 },
+// { "hud_glowingquotes", "enable/disable \"glowing\" quote text", (void *)&hud_glowingquotes, CVAR_BOOL, 0, 1 },
+// { "hud_scale","changes the hud scale", (void *)&ud.statusbarscale, CVAR_INT|CVAR_FUNCPTR, 36, 100 },
+// { "hud_showmapname", "enable/disable map name display on load", (void *)&hud_showmapname, CVAR_BOOL, 0, 1 },
+ { "hud_stats", "enable/disable level statistics display", (void *)&gLevelStats, CVAR_BOOL, 0, 1 },
+ { "hud_powerupduration", "enable/disable displaying the remaining seconds for power-ups", (void *)&gPowerupDuration, CVAR_BOOL, 0, 1 },
+ { "hud_showmaptitle", "enable/disable displaying the map title at the beginning of the maps", (void*)& gShowMapTitle, CVAR_BOOL, 0, 1 },
+// { "hud_textscale", "sets multiplayer chat message size", (void *)&ud.textscale, CVAR_INT, 100, 400 },
+// { "hud_weaponscale","changes the weapon scale", (void *)&ud.weaponscale, CVAR_INT, 10, 100 },
+// { "hud_statusbarmode", "change overlay mode of status bar", (void *)&ud.statusbarmode, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
+//
+//#ifdef EDUKE32_TOUCH_DEVICES
+// { "hud_hidestick", "hide the touch input stick", (void *)&droidinput.hideStick, CVAR_BOOL, 0, 1 },
+//#endif
+//
+ { "horizcenter", "enable/disable centered horizon line", (void *)&gCenterHoriz, CVAR_BOOL, 0, 1 },
+ { "deliriumblur", "enable/disable delirium blur effect(polymost)", (void *)&gDeliriumBlur, CVAR_BOOL, 0, 1 },
+ { "fov", "change the field of view", (void *)&gFov, CVAR_INT|CVAR_FUNCPTR, 75, 120 },
+ { "in_joystick","enables input from the joystick if it is present",(void *)&gSetup.usejoystick, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
+ { "in_mouse","enables input from the mouse if it is present",(void *)&gSetup.usemouse, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
+
+ { "in_aimmode", "0:toggle, 1:hold to aim", (void *)&gMouseAiming, CVAR_BOOL, 0, 1 },
+ {
+ "in_mousebias", "emulates the original mouse code's weighting of input towards whichever axis is moving the most at any given time",
+ (void *)&MouseBias, CVAR_INT, 0, 32
+ },
+ { "in_mousedeadzone", "amount of mouse movement to filter out", (void *)&MouseDeadZone, CVAR_INT, 0, 512 },
+ { "in_mouseflip", "invert vertical mouse movement", (void *)&gMouseAimingFlipped, CVAR_BOOL, 0, 1 },
+ { "in_mousemode", "toggles vertical mouse view", (void *)&gMouseAim, CVAR_BOOL, 0, 1 },
+ { "in_mousesmoothing", "enable/disable mouse input smoothing", (void *)&SmoothInput, CVAR_BOOL, 0, 1 },
+//
+ { "mus_enabled", "enables/disables music", (void *)&MusicToggle, CVAR_BOOL, 0, 1 },
+ { "mus_restartonload", "restart the music when loading a saved game with the same map or not", (void *)&MusicRestartsOnLoadToggle, CVAR_BOOL, 0, 1 },
+ { "mus_volume", "controls music volume", (void *)&MusicVolume, CVAR_INT, 0, 255 },
+ { "mus_device", "music device", (void *)&MusicDevice, CVAR_INT, 0, 1 },
+ { "mus_redbook", "enables/disables redbook audio", (void *)&CDAudioToggle, CVAR_BOOL, 0, 1 },
+//
+// { "osdhightile", "enable/disable hires art replacements for console text", (void *)&osdhightile, CVAR_BOOL, 0, 1 },
+// { "osdscale", "adjust console text size", (void *)&osdscale, CVAR_FLOAT|CVAR_FUNCPTR, 1, 4 },
+//
+// { "r_camrefreshdelay", "minimum delay between security camera sprite updates, 120 = 1 second", (void *)&ud.camera_time, CVAR_INT, 1, 240 },
+// { "r_drawweapon", "enable/disable weapon drawing", (void *)&ud.drawweapon, CVAR_INT, 0, 2 },
+ { "r_showfps", "show the frame rate counter", (void *)&gShowFps, CVAR_INT, 0, 3 },
+ { "r_showfpsperiod", "time in seconds before averaging min and max stats for r_showfps 2+", (void *)&gFramePeriod, CVAR_INT, 0, 5 },
+// { "r_shadows", "enable/disable sprite and model shadows", (void *)&ud.shadows, CVAR_BOOL, 0, 1 },
+ { "r_size", "change size of viewable area", (void *)&gViewSize, CVAR_INT|CVAR_FUNCPTR, 0, 7 },
+// { "r_rotatespritenowidescreen", "pass bit 1024 to all CON rotatesprite calls", (void *)&g_rotatespriteNoWidescreen, CVAR_BOOL|CVAR_FUNCPTR, 0, 1 },
+ { "r_upscalefactor", "increase performance by rendering at upscalefactor less than the screen resolution and upscale to the full resolution in the software renderer", (void *)&gUpscaleFactor, CVAR_INT|CVAR_FUNCPTR, 1, 16 },
+ { "r_precache", "enable/disable the pre-level caching routine", (void *)&useprecache, CVAR_BOOL, 0, 1 },
+//
+ { "r_ambientlight", "sets the global map light level",(void *)&r_ambientlight, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
+ { "r_maxfps", "limit the frame rate",(void *)&r_maxfps, CVAR_INT|CVAR_FUNCPTR, 0, 1000 },
+ { "r_maxfpsoffset", "menu-controlled offset for r_maxfps",(void *)&r_maxfpsoffset, CVAR_INT|CVAR_FUNCPTR, -10, 10 },
+
+ { "sensitivity","changes the mouse sensitivity", (void *)&CONTROL_MouseSensitivity, CVAR_FLOAT|CVAR_FUNCPTR, 0, 25 },
+//
+// { "skill","changes the game skill setting", (void *)&ud.m_player_skill, CVAR_INT|CVAR_FUNCPTR|CVAR_NOSAVE/*|CVAR_NOMULTI*/, 0, 5 },
+//
+// { "snd_ambience", "enables/disables ambient sounds", (void *)&ud.config.AmbienceToggle, CVAR_BOOL, 0, 1 },
+ { "snd_enabled", "enables/disables sound effects", (void *)&SoundToggle, CVAR_BOOL, 0, 1 },
+ { "snd_fxvolume", "controls volume for sound effects", (void *)&FXVolume, CVAR_INT, 0, 255 },
+ { "snd_mixrate", "sound mixing rate", (void *)&MixRate, CVAR_INT, 0, 48000 },
+ { "snd_numchannels", "the number of sound channels", (void *)&NumChannels, CVAR_INT, 0, 2 },
+ { "snd_numvoices", "the number of concurrent sounds", (void *)&NumVoices, CVAR_INT, 1, 128 },
+ { "snd_reversestereo", "reverses the stereo channels", (void *)&ReverseStereo, CVAR_BOOL, 0, 1 },
+ { "snd_doppler", "enable/disable 3d sound", (void *)&gDoppler, CVAR_BOOL, 0, 1 },
+// { "snd_speech", "enables/disables player speech", (void *)&ud.config.VoiceToggle, CVAR_INT, 0, 5 },
+//
+// { "team","change team in multiplayer", (void *)&ud.team, CVAR_INT|CVAR_MULTI, 0, 3 },
+//
+//#ifdef EDUKE32_TOUCH_DEVICES
+// { "touch_sens_move_x","touch input sensitivity for moving forward/back", (void *)&droidinput.forward_sens, CVAR_FLOAT, 1, 9 },
+// { "touch_sens_move_y","touch input sensitivity for strafing", (void *)&droidinput.strafe_sens, CVAR_FLOAT, 1, 9 },
+// { "touch_sens_look_x", "touch input sensitivity for turning left/right", (void *) &droidinput.yaw_sens, CVAR_FLOAT, 1, 9 },
+// { "touch_sens_look_y", "touch input sensitivity for looking up/down", (void *) &droidinput.pitch_sens, CVAR_FLOAT, 1, 9 },
+// { "touch_invert", "invert look up/down touch input", (void *) &droidinput.invertLook, CVAR_INT, 0, 1 },
+//#endif
+//
+ { "vid_gamma","adjusts gamma component of gamma ramp",(void *)&g_videoGamma, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
+ { "vid_contrast","adjusts contrast component of gamma ramp",(void *)&g_videoContrast, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
+ { "vid_brightness","adjusts brightness component of gamma ramp",(void *)&g_videoBrightness, CVAR_FLOAT|CVAR_FUNCPTR, 0, 10 },
+// { "wchoice","sets weapon autoselection order", (void *)ud.wchoice, CVAR_STRING|CVAR_FUNCPTR, 0, MAX_WEAPONS },
+ };
+//
+// osdcmd_cheatsinfo_stat.cheatnum = -1;
+//
+ for (auto & cv : cvars_game)
+ {
+ switch (cv.flags & (CVAR_FUNCPTR|CVAR_MULTI))
+ {
+ case CVAR_FUNCPTR:
+ OSD_RegisterCvar(&cv, osdcmd_cvar_set_game); break;
+ case CVAR_MULTI:
+ case CVAR_FUNCPTR|CVAR_MULTI:
+ OSD_RegisterCvar(&cv, osdcmd_cvar_set_multi); break;
+ default:
+ OSD_RegisterCvar(&cv, osdcmd_cvar_set); break;
+ }
+ }
+//
+// if (VOLUMEONE)
+// OSD_RegisterFunction("changelevel","changelevel : warps to the given level", osdcmd_changelevel);
+// else
+// {
+ OSD_RegisterFunction("changelevel","changelevel : warps to the given level", osdcmd_changelevel);
+ OSD_RegisterFunction("map","map : loads the given user map", osdcmd_map);
+ OSD_RegisterFunction("demo","demo : starts the given demo", osdcmd_demo);
+// }
+//
+// OSD_RegisterFunction("addpath","addpath : adds path to game filesystem", osdcmd_addpath);
+ OSD_RegisterFunction("bind",R"(bind : associates a keypress with a string of console input. Type "bind showkeys" for a list of keys and "listsymbols" for a list of valid console commands.)", osdcmd_bind);
+// OSD_RegisterFunction("cmenu","cmenu <#>: jumps to menu", osdcmd_cmenu);
+ OSD_RegisterFunction("crosshaircolor","crosshaircolor: changes the crosshair color", osdcmd_crosshaircolor);
+ OSD_RegisterFunction("crosshairreset", "crosshairreset: restores the original crosshair", osdcmd_resetcrosshair);
+//
+//#if !defined NETCODE_DISABLE
+// OSD_RegisterFunction("connect","connect: connects to a multiplayer game", osdcmd_connect);
+// OSD_RegisterFunction("disconnect","disconnect: disconnects from the local multiplayer game", osdcmd_disconnect);
+//#endif
+
+ for (auto & func : gamefunctions)
+ {
+ if (func[0] == '\0')
+ continue;
+
+// if (!Bstrcmp(gamefunctions[i],"Show_Console")) continue;
+
+ Bsprintf(buffer, "gamefunc_%s", func);
+
+ char *const t = Bstrtolower(Xstrdup(buffer));
+
+ Bstrcat(buffer, ": game button");
+
+ OSD_RegisterFunction(t, Xstrdup(buffer), osdcmd_button);
+ }
+
+ OSD_RegisterFunction("give","give : gives requested item", osdcmd_give);
+ OSD_RegisterFunction("god","god: toggles god mode", osdcmd_god);
+// OSD_RegisterFunction("activatecheat","activatecheat : activates a cheat code", osdcmd_activatecheat);
+//
+// OSD_RegisterFunction("initgroupfile","initgroupfile : adds a grp file into the game filesystem", osdcmd_initgroupfile);
+//#ifdef DEBUGGINGAIDS
+// OSD_RegisterFunction("inittimer","debug", osdcmd_inittimer);
+//#endif
+//#if !defined NETCODE_DISABLE
+// OSD_RegisterFunction("kick","kick : kicks a multiplayer client. See listplayers.", osdcmd_kick);
+// OSD_RegisterFunction("kickban","kickban : kicks a multiplayer client and prevents them from reconnecting. See listplayers.", osdcmd_kickban);
+//
+// OSD_RegisterFunction("listplayers","listplayers: lists currently connected multiplayer clients", osdcmd_listplayers);
+//#endif
+ OSD_RegisterFunction("music","music EL: change music", osdcmd_music);
+//
+//#if !defined NETCODE_DISABLE
+// OSD_RegisterFunction("name","name: change your multiplayer nickname", osdcmd_name);
+//#endif
+//
+ OSD_RegisterFunction("noclip","noclip: toggles clipping mode", osdcmd_noclip);
+//
+//#if !defined NETCODE_DISABLE
+// OSD_RegisterFunction("password","password: sets multiplayer game password", osdcmd_password);
+//#endif
+//
+// OSD_RegisterFunction("printtimes", "printtimes: prints VM timing statistics", osdcmd_printtimes);
+//
+// OSD_RegisterFunction("purgesaves", "purgesaves: deletes obsolete and unreadable save files", osdcmd_purgesaves);
+//
+// OSD_RegisterFunction("quicksave","quicksave: performs a quick save", osdcmd_quicksave);
+// OSD_RegisterFunction("quickload","quickload: performs a quick load", osdcmd_quickload);
+ OSD_RegisterFunction("quit","quit: exits the game immediately", osdcmd_quit);
+ OSD_RegisterFunction("exit","exit: exits the game immediately", osdcmd_quit);
+//
+// OSD_RegisterFunction("restartmap", "restartmap: restarts the current map", osdcmd_restartmap);
+ OSD_RegisterFunction("restartsound","restartsound: reinitializes the sound system",osdcmd_restartsound);
+ OSD_RegisterFunction("restartvid","restartvid: reinitializes the video mode",osdcmd_restartvid);
+//#if !defined LUNATIC
+// OSD_RegisterFunction("addlogvar","addlogvar : prints the value of a gamevar", osdcmd_addlogvar);
+// OSD_RegisterFunction("setvar","setvar : sets the value of a gamevar", osdcmd_setvar);
+// OSD_RegisterFunction("setvarvar","setvarvar : sets the value of to ", osdcmd_setvar);
+// OSD_RegisterFunction("setactorvar","setactorvar : sets the value of 's to ", osdcmd_setactorvar);
+//#else
+// OSD_RegisterFunction("lua", "lua \"Lua code...\": runs Lunatic code", osdcmd_lua);
+//#endif
+ OSD_RegisterFunction("screenshot","screenshot [format]: takes a screenshot.", osdcmd_screenshot);
+//
+// OSD_RegisterFunction("spawn","spawn [palnum] [cstat] [ang] [x y z]: spawns a sprite with the given properties",osdcmd_spawn);
+
+ OSD_RegisterFunction("unbind","unbind : unbinds a key", osdcmd_unbind);
+ OSD_RegisterFunction("unbindall","unbindall: unbinds all keys", osdcmd_unbindall);
+
+ OSD_RegisterFunction("vidmode","vidmode : change the video mode",osdcmd_vidmode);
+#ifdef USE_OPENGL
+ baselayer_osdcmd_vidmode_func = osdcmd_vidmode;
+#endif
+//
+//#ifndef NETCODE_DISABLE
+// OSD_RegisterFunction("dumpmapstates", "Dumps current snapshots to CL/Srv_MapStates.bin", osdcmd_dumpmapstate);
+// OSD_RegisterFunction("playerinfo", "Prints information about the current player", osdcmd_playerinfo);
+//#endif
+
+ return 0;
+}
+
+void GAME_onshowosd(int shown)
+{
+ // G_UpdateScreenArea();
+
+ mouseLockToWindow((!shown) + 2);
+
+ //osdshown = shown;
+
+ // XXX: it's weird to fake a keypress like this.
+// if (numplayers == 1 && ((shown && !ud.pause_on) || (!shown && ud.pause_on)))
+// KB_KeyDown[sc_Pause] = 1;
+}
+
+void GAME_clearbackground(int numcols, int numrows)
+{
+ COMMON_clearbackground(numcols, numrows);
+}
+
diff --git a/source/blood/src/osdcmds.h b/source/blood/src/osdcmds.h
new file mode 100644
index 000000000..14fe89edf
--- /dev/null
+++ b/source/blood/src/osdcmds.h
@@ -0,0 +1,50 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//struct osdcmd_cheatsinfo {
+// int32_t cheatnum; // -1 = none, else = see DoCheats()
+// int32_t volume,level;
+//};
+//
+//extern struct osdcmd_cheatsinfo osdcmd_cheatsinfo_stat;
+
+int32_t registerosdcommands(void);
+void onvideomodechange(int32_t newmode);
+void GAME_onshowosd(int32_t shown);
+void GAME_clearbackground(int32_t numcols, int32_t numrows);
+
+// extern float r_ambientlight,r_ambientlightrecip;
+
+extern const char *const ConsoleButtons[];
+
+extern uint32_t cl_cheatmask;
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/source/blood/src/player.cpp b/source/blood/src/player.cpp
new file mode 100644
index 000000000..300144b68
--- /dev/null
+++ b/source/blood/src/player.cpp
@@ -0,0 +1,2400 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include "compat.h"
+#include "build.h"
+#include "mmulti.h"
+#include "actor.h"
+#include "blood.h"
+#include "callback.h"
+#include "config.h"
+#include "controls.h"
+#include "demo.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gib.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "map2d.h"
+#include "network.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "tile.h"
+#include "triggers.h"
+#include "trig.h"
+#include "view.h"
+#include "warp.h"
+#include "weapon.h"
+#include "common_game.h"
+
+PROFILE gProfile[kMaxPlayers];
+
+PLAYER gPlayer[kMaxPlayers];
+PLAYER *gMe, *gView;
+
+POWERUPINFO gPowerUpInfo[kMaxPowerUps] = {
+ { -1, 1, 1, 1 },
+ { -1, 1, 1, 1 },
+ { -1, 1, 1, 1 },
+ { -1, 1, 1, 1 },
+ { -1, 1, 1, 1 },
+ { -1, 1, 1, 1 },
+ { -1, 1, 1, 1 },
+ { -1, 0, 100, 100 },
+ { -1, 0, 50, 100 },
+ { -1, 0, 20, 100 },
+ { -1, 0, 100, 200 },
+ { -1, 0, 2, 200 },
+ { 783, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 }, // 13: cloak of invisibility
+ { -1, 1, 3600, 432000 }, // 14: death mask (invulnerability)
+ { 827, 0, 3600, 432000 }, // 15: jump boots
+ { 828, 0, 3600, 432000 },
+ { -1, 0, 3600, 1728000 }, // 17: guns akimbo
+ { -1, 0, 3600, 432000 }, // 18: diving suit
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 }, // 21: crystal ball
+ { -1, 0, 3600, 432000 },
+ { 851, 0, 3600, 432000 },
+ { 2428, 0, 3600, 432000 }, // 24: reflective shots
+ { -1, 0, 3600, 432000 }, // 25: beast vision
+ { -1, 0, 3600, 432000 }, // 26: cloak of shadow
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 900, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 0, 3600, 432000 },
+ { -1, 1, 3600, 432000 },
+ { -1, 0, 1, 432000 },
+ { -1, 0, 1, 432000 },
+ { -1, 0, 1, 432000 },
+ { -1, 0, 1, 432000 },
+ { -1, 0, 1, 432000 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 },
+ { 0, 0, 0, 0 }, // dummy
+ { -1, 1, 1, 1 } // kGDXItemLevelMap
+};
+
+int Handicap[] = {
+ 144, 208, 256, 304, 368
+};
+
+POSTURE gPosture[4][3] = {
+
+ // normal human
+ {
+ {
+ 0x4000,
+ 0x4000,
+ 0x4000,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 0x1600,
+ 0x1200,
+ 0xc00,
+ 0x90
+ },
+ {
+ 0x1200,
+ 0x1200,
+ 0x1200,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 0x1400,
+ 0x1000,
+ -0x600,
+ 0xb0
+ },
+ {
+ 0x2000,
+ 0x2000,
+ 0x2000,
+ 22,
+ 28,
+ 24,
+ 16,
+ 16,
+ 40,
+ 0x800,
+ 0x600,
+ -0x600,
+ 0xb0
+ },
+ },
+
+ // normal beast
+ {
+ {
+ 0x4000,
+ 0x4000,
+ 0x4000,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 0x1600,
+ 0x1200,
+ 0xc00,
+ 0x90
+ },
+ {
+ 0x1200,
+ 0x1200,
+ 0x1200,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 0x1400,
+ 0x1000,
+ -0x600,
+ 0xb0
+ },
+ {
+ 0x2000,
+ 0x2000,
+ 0x2000,
+ 22,
+ 28,
+ 24,
+ 16,
+ 16,
+ 40,
+ 0x800,
+ 0x600,
+ -0x600,
+ 0xb0
+ },
+ },
+
+ // shrink human
+ {
+ {
+ 10384,
+ 12384,
+ 12384,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 5632,
+ 4608,
+ 3072,
+ 144
+ },
+ {
+ 2108,
+ 2108,
+ 2108,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 5120,
+ 4096,
+ -1536,
+ 176
+ },
+ {
+ 2192,
+ 3192,
+ 4192,
+ 22,
+ 28,
+ 24,
+ 16,
+ 16,
+ 40,
+ 2048,
+ 1536,
+ -1536,
+ 176
+ },
+ },
+
+ // grown human
+ {
+ {
+ 19384,
+ 15384,
+ 15384,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 5632,
+ 4608,
+ 3072,
+ 144
+ },
+ {
+ 5608,
+ 5608,
+ 5608,
+ 14,
+ 17,
+ 24,
+ 16,
+ 32,
+ 80,
+ 5120,
+ 4096,
+ -1536,
+ 176
+ },
+ {
+ 11192,
+ 11192,
+ 11192,
+ 22,
+ 28,
+ 24,
+ 16,
+ 16,
+ 40,
+ 2048,
+ 1536,
+ -1536,
+ 176
+ },
+ },
+};
+
+AMMOINFO gAmmoInfo[] = {
+ { 0, -1 },
+ { 100, -1 },
+ { 100, 4 },
+ { 500, 5 },
+ { 100, -1 },
+ { 50, -1 },
+ { 2880, -1 },
+ { 250, -1 },
+ { 100, -1 },
+ { 100, -1 },
+ { 50, -1 },
+ { 50, -1 },
+};
+
+struct ARMORDATA {
+ int at0;
+ int at4;
+ int at8;
+ int atc;
+ int at10;
+ int at14;
+};
+ARMORDATA armorData[5] = {
+ { 0x320, 0x640, 0x320, 0x640, 0x320, 0x640 },
+ { 0x640, 0x640, 0, 0x640, 0, 0x640 },
+ { 0, 0x640, 0x640, 0x640, 0, 0x640 },
+ { 0, 0x640, 0, 0x640, 0x640, 0x640 },
+ { 0xc80, 0xc80, 0xc80, 0xc80, 0xc80, 0xc80 }
+};
+
+void PlayerSurvive(int, int);
+void PlayerKeelsOver(int, int);
+
+int nPlayerSurviveClient = seqRegisterClient(PlayerSurvive);
+int nPlayerKeelClient = seqRegisterClient(PlayerKeelsOver);
+
+struct VICTORY {
+ const char *at0;
+ int at4;
+};
+
+VICTORY gVictory[] = {
+ { "%s boned %s like a fish", 4100 },
+ { "%s castrated %s", 4101 },
+ { "%s creamed %s", 4102 },
+ { "%s destroyed %s", 4103 },
+ { "%s diced %s", 4104 },
+ { "%s disemboweled %s", 4105 },
+ { "%s flattened %s", 4106 },
+ { "%s gave %s Anal Justice", 4107 },
+ { "%s gave AnAl MaDnEsS to %s", 4108 },
+ { "%s hurt %s real bad", 4109 },
+ { "%s killed %s", 4110 },
+ { "%s made mincemeat out of %s", 4111 },
+ { "%s massacred %s", 4112 },
+ { "%s mutilated %s", 4113 },
+ { "%s reamed %s", 4114 },
+ { "%s ripped %s a new orifice", 4115 },
+ { "%s slaughtered %s", 4116 },
+ { "%s sliced %s", 4117 },
+ { "%s smashed %s", 4118 },
+ { "%s sodomized %s", 4119 },
+ { "%s splattered %s", 4120 },
+ { "%s squashed %s", 4121 },
+ { "%s throttled %s", 4122 },
+ { "%s wasted %s", 4123 },
+ { "%s body bagged %s", 4124 },
+};
+
+struct SUICIDE {
+ const char *at0;
+ int at4;
+};
+
+SUICIDE gSuicide[] = {
+ { "%s is excrement", 4202 },
+ { "%s is hamburger", 4203 },
+ { "%s suffered scrotum separation", 4204 },
+ { "%s volunteered for population control", 4206 },
+ { "%s has suicided", 4207 },
+};
+
+struct DAMAGEINFO {
+ int at0;
+ int at4[3];
+ int at10[3];
+};
+
+DAMAGEINFO damageInfo[7] = {
+ { -1, 731, 732, 733, 710, 710, 710 },
+ { 1, 742, 743, 744, 711, 711, 711 },
+ { 0, 731, 732, 733, 712, 712, 712 },
+ { 1, 731, 732, 733, 713, 713, 713 },
+ { -1, 724, 724, 724, 714, 714, 714 },
+ { 2, 731, 732, 733, 715, 715, 715 },
+ { 0, 0, 0, 0, 0, 0, 0 }
+};
+
+int powerupCheck(PLAYER *pPlayer, int nPowerUp)
+{
+ dassert(pPlayer != NULL);
+ dassert(nPowerUp >= 0 && nPowerUp < kMaxPowerUps);
+ int nPack = powerupToPackItem(nPowerUp);
+ if (nPack >= 0 && !packItemActive(pPlayer, nPack))
+ return 0;
+ return pPlayer->at202[nPowerUp];
+}
+
+bool isGrown(spritetype* pSprite) {
+ return (powerupCheck(&gPlayer[pSprite->type - kDudePlayer1], 29) > 0);
+}
+
+bool isShrinked(spritetype* pSprite) {
+ return (powerupCheck(&gPlayer[pSprite->type - kDudePlayer1], 30) > 0);
+}
+
+bool shrinkPlayerSize(PLAYER* pPlayer, int divider) {
+ pPlayer->pXSprite->scale = -divider;
+ playerSetRace(pPlayer, kModeHumanShrink);
+ return true;
+}
+
+bool growPlayerSize(PLAYER* pPlayer, int multiplier) {
+ pPlayer->pXSprite->scale = multiplier;
+ playerSetRace(pPlayer, kModeHumanGrown);
+ return true;
+}
+
+bool resetPlayerSize(PLAYER* pPlayer) {
+ playerSetRace(pPlayer, kModeHuman);
+ pPlayer->pXSprite->scale = 0;
+ return true;
+}
+
+void deactivateSizeShrooms(PLAYER* pPlayer) {
+ powerupDeactivate(pPlayer, 29);
+ pPlayer->at202[29] = 0;
+
+ powerupDeactivate(pPlayer, 30);
+ pPlayer->at202[30] = 0;
+}
+
+char powerupActivate(PLAYER *pPlayer, int nPowerUp)
+{
+ if (powerupCheck(pPlayer, nPowerUp) > 0 && gPowerUpInfo[nPowerUp].at2)
+ return 0;
+ if (!pPlayer->at202[nPowerUp])
+ pPlayer->at202[nPowerUp] = gPowerUpInfo[nPowerUp].at3;
+ int nPack = powerupToPackItem(nPowerUp);
+ if (nPack >= 0)
+ pPlayer->packInfo[nPack].at0 = 1;
+ switch (nPowerUp+100)
+ {
+ case kGDXItemMapLevel:
+ gFullMap = true;
+ break;
+ case 130:
+ if (isGrown(pPlayer->pSprite)) deactivateSizeShrooms(pPlayer);
+ else shrinkPlayerSize(pPlayer, 2);
+ break;
+ case 129:
+ if (isShrinked(pPlayer->pSprite)) deactivateSizeShrooms(pPlayer);
+ else {
+ growPlayerSize(pPlayer, 2);
+ if (powerupCheck(&gPlayer[pPlayer->pSprite->type - kDudePlayer1], 13) > 0) {
+ powerupDeactivate(pPlayer, 13);
+ pPlayer->at202[13] = 0;
+ }
+
+ if (ceilIsTooLow(pPlayer->pSprite))
+ actDamageSprite(pPlayer->pSprite->xvel, pPlayer->pSprite, DAMAGE_TYPE_3, 65535);
+ }
+ break;
+ case 112:
+ case 115: // jump boots
+ pPlayer->ata1[0]++;
+ break;
+ case 124: // reflective shots
+ if (pPlayer == gMe && gGameOptions.nGameType == 0)
+ sfxSetReverb2(1);
+ break;
+ case 114: // death mask
+ for (int i = 0; i < 7; i++)
+ pPlayer->ata1[i]++;
+ break;
+ case 118: // diving suit
+ pPlayer->ata1[4]++;
+ if (pPlayer == gMe && gGameOptions.nGameType == 0)
+ sfxSetReverb(1);
+ break;
+ case 119:
+ pPlayer->ata1[4]++;
+ break;
+ case 139:
+ pPlayer->ata1[1]++;
+ break;
+ case 117: // guns akimbo
+ pPlayer->atc.newWeapon = pPlayer->atbd;
+ WeaponRaise(pPlayer);
+ break;
+ }
+ sfxPlay3DSound(pPlayer->pSprite, 776, -1, 0);
+ return 1;
+}
+
+void powerupDeactivate(PLAYER *pPlayer, int nPowerUp)
+{
+ int nPack = powerupToPackItem(nPowerUp);
+ if (nPack >= 0)
+ pPlayer->packInfo[nPack].at0 = 0;
+ switch (nPowerUp+100)
+ {
+ case 130:
+ resetPlayerSize(pPlayer);
+ if (ceilIsTooLow(pPlayer->pSprite))
+ actDamageSprite(pPlayer->pSprite->xvel, pPlayer->pSprite, DAMAGE_TYPE_3, 65535);
+ break;
+ case 129:
+ resetPlayerSize(pPlayer);
+ break;
+ case 112:
+ case 115: // jump boots
+ pPlayer->ata1[0]--;
+ break;
+ case 114: // death mask
+ for (int i = 0; i < 7; i++)
+ pPlayer->ata1[i]--;
+ break;
+ case 118: // diving suit
+ pPlayer->ata1[4]--;
+ if (pPlayer == gMe)
+ sfxSetReverb(0);
+ break;
+ case 124: // reflective shots
+ if (pPlayer == gMe)
+ sfxSetReverb(0);
+ break;
+ case 119:
+ pPlayer->ata1[4]--;
+ break;
+ case 139:
+ pPlayer->ata1[1]--;
+ break;
+ case 117: // guns akimbo
+ pPlayer->atc.newWeapon = pPlayer->atbd;
+ WeaponRaise(pPlayer);
+ break;
+ }
+}
+
+void powerupSetState(PLAYER *pPlayer, int nPowerUp, char bState)
+{
+ if (!bState)
+ powerupActivate(pPlayer, nPowerUp);
+ else
+ powerupDeactivate(pPlayer, nPowerUp);
+}
+
+void powerupProcess(PLAYER *pPlayer)
+{
+ pPlayer->at31d = ClipLow(pPlayer->at31d-4, 0);
+ for (int i = kMaxPowerUps-1; i >= 0; i--)
+ {
+ int nPack = powerupToPackItem(i);
+ if (nPack >= 0)
+ {
+ if (pPlayer->packInfo[nPack].at0)
+ {
+ pPlayer->at202[i] = ClipLow(pPlayer->at202[i]-4, 0);
+ if (pPlayer->at202[i])
+ pPlayer->packInfo[nPack].at1 = (100*pPlayer->at202[i])/gPowerUpInfo[i].at3;
+ else
+ {
+ powerupDeactivate(pPlayer, i);
+ if (pPlayer->at321 == nPack)
+ pPlayer->at321 = 0;
+ }
+ }
+ }
+ else if (pPlayer->at202[i] > 0)
+ {
+ pPlayer->at202[i] = ClipLow(pPlayer->at202[i]-4, 0);
+ if (!pPlayer->at202[i])
+ powerupDeactivate(pPlayer, i);
+ }
+ }
+}
+
+void powerupClear(PLAYER *pPlayer)
+{
+ for (int i = kMaxPowerUps-1; i >= 0; i--)
+ {
+ pPlayer->at202[i] = 0;
+ }
+}
+
+void powerupInit(void)
+{
+}
+
+int packItemToPowerup(int nPack)
+{
+ int nPowerUp = -1;
+ switch (nPack)
+ {
+ case 0:
+ break;
+ case 1:
+ nPowerUp = 18;
+ break;
+ case 2:
+ nPowerUp = 21;
+ break;
+ case 3:
+ nPowerUp = 25;
+ break;
+ case 4:
+ nPowerUp = 15;
+ break;
+ default:
+ ThrowError("Unhandled pack item %d", nPack);
+ break;
+ }
+ return nPowerUp;
+}
+
+int powerupToPackItem(int nPowerUp)
+{
+ const int jumpBoots = 15;
+ const int divingSuit = 18;
+ const int crystalBall = 21;
+ const int beastVision = 25;
+
+ switch (nPowerUp)
+ {
+ case divingSuit:
+ return 1;
+ case crystalBall:
+ return 2;
+ case beastVision:
+ return 3;
+ case jumpBoots:
+ return 4;
+ }
+ return -1;
+}
+
+char packAddItem(PLAYER *pPlayer, unsigned int nPack)
+{
+ if (nPack <= 4)
+ {
+ if (pPlayer->packInfo[nPack].at1 >= 100)
+ return 0;
+ pPlayer->packInfo[nPack].at1 = 100;
+ int nPowerUp = packItemToPowerup(nPack);
+ if (nPowerUp >= 0)
+ pPlayer->at202[nPowerUp] = gPowerUpInfo[nPowerUp].at3;
+ if (pPlayer->at321 == -1)
+ pPlayer->at321 = nPack;
+ if (!pPlayer->packInfo[pPlayer->at321].at1)
+ pPlayer->at321 = nPack;
+ }
+ else
+ ThrowError("Unhandled pack item %d", nPack);
+ return 1;
+}
+
+int packCheckItem(PLAYER *pPlayer, int nPack)
+{
+ return pPlayer->packInfo[nPack].at1;
+}
+
+char packItemActive(PLAYER *pPlayer, int nPack)
+{
+ return pPlayer->packInfo[nPack].at0;
+}
+
+void packUseItem(PLAYER *pPlayer, int nPack)
+{
+ char v4 = 0;
+ int nPowerUp = -1;
+ if (pPlayer->packInfo[nPack].at1 > 0)
+ {
+ switch (nPack)
+ {
+ case 0:
+ {
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ unsigned int health = pXSprite->health>>4;
+ if (health < 100)
+ {
+ int heal = ClipHigh(100-health, pPlayer->packInfo[0].at1);
+ actHealDude(pXSprite, heal, 100);
+ pPlayer->packInfo[0].at1 -= heal;
+ }
+ break;
+ }
+ case 1:
+ v4 = 1;
+ nPowerUp = 18;
+ break;
+ case 2:
+ v4 = 1;
+ nPowerUp = 21;
+ break;
+ case 3:
+ v4 = 1;
+ nPowerUp = 25;
+ break;
+ case 4:
+ v4 = 1;
+ nPowerUp = 15;
+ break;
+ default:
+ ThrowError("Unhandled pack item %d", nPack);
+ return;
+ }
+ }
+ pPlayer->at31d = 0;
+ if (v4)
+ powerupSetState(pPlayer, nPowerUp, pPlayer->packInfo[nPack].at0);
+}
+
+void packPrevItem(PLAYER *pPlayer)
+{
+ if (pPlayer->at31d > 0)
+ {
+ for (int nPrev = ClipLow(pPlayer->at321-1,0); nPrev >= 0; nPrev--)
+ {
+ if (pPlayer->packInfo[nPrev].at1)
+ {
+ pPlayer->at321 = nPrev;
+ break;
+ }
+ }
+ }
+ pPlayer->at31d = 600;
+}
+
+void packNextItem(PLAYER *pPlayer)
+{
+ if (pPlayer->at31d > 0)
+ {
+ for (int nNext = ClipHigh(pPlayer->at321+1,5); nNext < 5; nNext++)
+ {
+ if (pPlayer->packInfo[nNext].at1)
+ {
+ pPlayer->at321 = nNext;
+ break;
+ }
+ }
+ }
+ pPlayer->at31d = 600;
+}
+
+char playerSeqPlaying(PLAYER * pPlayer, int nSeq)
+{
+ int nCurSeq = seqGetID(3, pPlayer->pSprite->extra);
+ if (pPlayer->pDudeInfo->seqStartID+nSeq == nCurSeq && seqGetStatus(3,pPlayer->pSprite->extra) >= 0)
+ return 1;
+ return 0;
+}
+
+void playerSetRace(PLAYER *pPlayer, int nLifeMode)
+{
+ dassert(nLifeMode >= kModeHuman && nLifeMode <= kModeHumanGrown);
+ DUDEINFO *pDudeInfo = pPlayer->pDudeInfo;
+ *pDudeInfo = gPlayerTemplate[nLifeMode];
+ pPlayer->at5f = nLifeMode;
+
+ // By NoOne: don't forget to change clipdist for grow and shrink modes
+ pPlayer->pSprite->clipdist = pDudeInfo->clipdist;
+
+ for (int i = 0; i < 7; i++)
+ pDudeInfo->at70[i] = mulscale8(Handicap[gProfile[pPlayer->at57].skill], pDudeInfo->startDamage[i]);
+}
+
+void playerSetGodMode(PLAYER *pPlayer, char bGodMode)
+{
+ if (bGodMode)
+ {
+ for (int i = 0; i < 7; i++)
+ pPlayer->ata1[i]++;
+ }
+ else
+ {
+ for (int i = 0; i < 7; i++)
+ pPlayer->ata1[i]--;
+ }
+ pPlayer->at31a = bGodMode;
+}
+
+void playerResetInertia(PLAYER *pPlayer)
+{
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][pPlayer->at2f];
+ pPlayer->at67 = pPlayer->pSprite->z-pPosture->at24;
+ pPlayer->at6f = pPlayer->pSprite->z-pPosture->at28;
+ viewBackupView(pPlayer->at57);
+}
+
+void playerResetPowerUps(PLAYER* pPlayer)
+{
+ const int jumpBoots = 15;
+ const int divingSuit = 18;
+ const int crystalBall = 21;
+ const int beastVision = 25;
+
+ for (int i = 0; i < kMaxPowerUps; i++)
+ {
+ if (!VanillaMode()
+ && (i == jumpBoots
+ || i == divingSuit
+ || i == crystalBall
+ || i == beastVision))
+ continue;
+
+ pPlayer->at202[i] = 0;
+ }
+}
+
+void playerStart(int nPlayer)
+{
+ PLAYER* pPlayer = &gPlayer[nPlayer];
+ GINPUT* pInput = &pPlayer->atc;
+ ZONE* pStartZone = NULL;
+
+ // normal start position
+ if (gGameOptions.nGameType <= 1)
+ pStartZone = &gStartZone[nPlayer];
+
+ // By NoOne: let's check if there is positions of teams is specified
+ // if no, pick position randomly, just like it works in vanilla.
+ else if (gGameOptions.nGameType == 3 && gTeamsSpawnUsed == true) {
+ int maxRetries = 5;
+ while (maxRetries-- > 0) {
+ if (pPlayer->at2ea == 0) pStartZone = &gStartZoneTeam1[Random(3)];
+ else pStartZone = &gStartZoneTeam2[Random(3)];
+
+ if (maxRetries != 0) {
+ // check if there is no spawned player in selected zone
+ for (int i = headspritesect[pStartZone->sectnum]; i >= 0; i = nextspritesect[i]) {
+ spritetype* pSprite = &sprite[i];
+ if (pStartZone->x == pSprite->x && pStartZone->y == pSprite->y && IsPlayerSprite(pSprite)) {
+ pStartZone = NULL;
+ break;
+ }
+ }
+ }
+
+ if (pStartZone != NULL)
+ break;
+ }
+ } else {
+ pStartZone = &gStartZone[Random(8)];
+ }
+
+ spritetype *pSprite = actSpawnSprite(pStartZone->sectnum, pStartZone->x, pStartZone->y, pStartZone->z, 6, 1);
+ dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ pPlayer->pSprite = pSprite;
+ pPlayer->pXSprite = pXSprite;
+ pPlayer->at5b = pSprite->index;
+ DUDEINFO *pDudeInfo = &dudeInfo[kDudePlayer1 + nPlayer - kDudeBase];
+ pPlayer->pDudeInfo = pDudeInfo;
+ playerSetRace(pPlayer, kModeHuman);
+ seqSpawn(pDudeInfo->seqStartID, 3, pSprite->extra, -1);
+ if (pPlayer == gMe)
+ SetBitString(show2dsprite, pSprite->index);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ pSprite->z -= bottom - pSprite->z;
+ pSprite->pal = 11+(pPlayer->at2ea&3);
+ pPlayer->angold = pSprite->ang = pStartZone->ang;
+ pPlayer->q16ang = fix16_from_int(pSprite->ang);
+ pSprite->type = kDudePlayer1+nPlayer;
+ pSprite->clipdist = pDudeInfo->clipdist;
+ pSprite->hitag = 15;
+ pXSprite->burnTime = 0;
+ pXSprite->burnSource = -1;
+ pPlayer->pXSprite->health = pDudeInfo->startHealth<<4;
+ pPlayer->pSprite->cstat &= (unsigned short)~32768;
+ pPlayer->at63 = 0;
+ pPlayer->q16horiz = 0;
+ pPlayer->q16slopehoriz = 0;
+ pPlayer->q16look = 0;
+ pPlayer->at83 = 0;
+ pPlayer->at2ee = -1;
+ pPlayer->at2f2 = 1200;
+ pPlayer->at2f6 = 0;
+ pPlayer->at2fa = 0;
+ pPlayer->at2fe = 0;
+ pPlayer->at302 = 0;
+ pPlayer->at306 = 0;
+ pPlayer->at30a = 0;
+ pPlayer->at30e = 0;
+ pPlayer->at312 = 0;
+ pPlayer->at316 = 0;
+ pPlayer->at2f = 0;
+ pPlayer->voodooTarget = -1;
+ pPlayer->at34e = 0;
+ pPlayer->at352 = 0;
+ pPlayer->at356 = 0;
+ playerResetInertia(pPlayer);
+ pPlayer->at73 = 0;
+ pPlayer->at1ca.dx = 0x4000;
+ pPlayer->at1ca.dy = 0;
+ pPlayer->at1ca.dz = 0;
+ pPlayer->at1d6 = -1;
+ pPlayer->at6b = pPlayer->at73;
+ for (int i = 0; i < 8; i++)
+ pPlayer->at88[i] = gGameOptions.nGameType >= 2;
+ pPlayer->at90 = 0;
+ for (int i = 0; i < 8; i++)
+ pPlayer->at91[i] = -1;
+ for (int i = 0; i < 7; i++)
+ pPlayer->ata1[i] = 0;
+ if (pPlayer->at31a)
+ playerSetGodMode(pPlayer, 1);
+ gInfiniteAmmo = 0;
+ gFullMap = 0;
+ pPlayer->at1ba = 0;
+ pPlayer->at1fe = 0;
+ pPlayer->atbe = 0;
+ xvel[pSprite->index] = yvel[pSprite->index] = zvel[pSprite->index] = 0;
+ pInput->q16turn = 0;
+ pInput->keyFlags.word = 0;
+ pInput->forward = 0;
+ pInput->strafe = 0;
+ pInput->q16mlook = 0;
+ pInput->buttonFlags.byte = 0;
+ pInput->useFlags.byte = 0;
+ pPlayer->at35a = 0;
+ pPlayer->at37f = 0;
+ pPlayer->at35e = 0;
+ pPlayer->at362 = 0;
+ pPlayer->at366 = 0;
+ pPlayer->at36a = 0;
+ pPlayer->at36e = 0;
+ pPlayer->at372 = 0;
+ pPlayer->atbf = 0;
+ pPlayer->atc3 = 0;
+ pPlayer->at26 = -1;
+ pPlayer->at376 = 0;
+ pPlayer->nWaterPal = 0;
+ playerResetPowerUps(pPlayer);
+
+ if (pPlayer == gMe)
+ {
+ viewInitializePrediction();
+ gViewMap.x = pPlayer->pSprite->x;
+ gViewMap.y = pPlayer->pSprite->y;
+ gViewMap.angle = pPlayer->pSprite->ang;
+ }
+ if (IsUnderwaterSector(pSprite->sectnum))
+ {
+ pPlayer->at2f = 1;
+ pPlayer->pXSprite->medium = 1;
+ }
+}
+
+void playerReset(PLAYER *pPlayer)
+{
+ static int dword_136400[] = {
+ 3, 4, 2, 8, 9, 10, 7, 1, 1, 1, 1, 1, 1, 1
+ };
+ static int dword_136438[] = {
+ 3, 4, 2, 8, 9, 10, 7, 1, 1, 1, 1, 1, 1, 1
+ };
+ dassert(pPlayer != NULL);
+ for (int i = 0; i < 14; i++)
+ {
+ pPlayer->atcb[i] = gInfiniteAmmo;
+ pPlayer->atd9[i] = 0;
+ }
+ pPlayer->atcb[1] = 1;
+ pPlayer->atbd = 0;
+ pPlayer->at2a = -1;
+ pPlayer->atc.newWeapon = 1;
+ for (int i = 0; i < 14; i++)
+ {
+ pPlayer->at111[0][i] = dword_136400[i];
+ pPlayer->at111[1][i] = dword_136438[i];
+ }
+ for (int i = 0; i < 12; i++)
+ {
+ if (gInfiniteAmmo)
+ pPlayer->at181[i] = gAmmoInfo[i].at0;
+ else
+ pPlayer->at181[i] = 0;
+ }
+ for (int i = 0; i < 3; i++)
+ pPlayer->at33e[i] = 0;
+ pPlayer->atbf = 0;
+ pPlayer->atc3 = 0;
+ pPlayer->at26 = -1;
+ pPlayer->at1b1 = 0;
+ pPlayer->at321 = -1;
+ for (int i = 0; i < 5; i++)
+ {
+ pPlayer->packInfo[i].at0 = 0;
+ pPlayer->packInfo[i].at1 = 0;
+ }
+}
+
+int dword_21EFB0[8];
+int dword_21EFD0[8];
+
+void playerInit(int nPlayer, unsigned int a2)
+{
+ PLAYER *pPlayer = &gPlayer[nPlayer];
+ if (!(a2&1))
+ memset(pPlayer, 0, sizeof(PLAYER));
+ pPlayer->at57 = nPlayer;
+ pPlayer->at2ea = nPlayer;
+ if (gGameOptions.nGameType == 3)
+ pPlayer->at2ea = nPlayer&1;
+ pPlayer->at2c6 = 0;
+ memset(dword_21EFB0, 0, sizeof(dword_21EFB0));
+ memset(dword_21EFD0, 0, sizeof(dword_21EFD0));
+ memset(pPlayer->at2ca, 0, sizeof(pPlayer->at2ca));
+ if (!(a2&1))
+ playerReset(pPlayer);
+}
+
+char sub_3A158(PLAYER *a1, spritetype *a2)
+{
+ for (int nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ if (a2 && a2->index == nSprite)
+ continue;
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->type == 431 && actOwnerIdToSpriteId(pSprite->owner) == a1->at5b)
+ return 1;
+ }
+ return 0;
+}
+
+char PickupItem(PLAYER *pPlayer, spritetype *pItem)
+{
+ char buffer[80];
+ int pickupSnd = 775;
+ spritetype *pSprite = pPlayer->pSprite;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ int nType = pItem->type - 100;
+ switch (pItem->type)
+ {
+ //case 129:
+ //dudeInfo[31].seqStartID = 13568;
+ //if (!powerupActivate(pPlayer, nType))
+ //return 0;
+ //return 1;
+ case 113:
+ if (isGrown(pPlayer->pSprite)) return false;
+ case 130:
+ case 129:
+ switch (pItem->type) {
+ case 130:
+ if (isShrinked(pSprite)) return false;
+ break;
+ case 129:
+ if (isGrown(pSprite)) return false;
+ break;
+ }
+ powerupActivate(pPlayer, nType);
+ break;
+ case 145:
+ case 146:
+ if (gGameOptions.nGameType != 3)
+ return 0;
+ if (pItem->extra > 0)
+ {
+ XSPRITE *pXItem = &xsprite[pItem->extra];
+ if (pItem->type == 145)
+ {
+ if (pPlayer->at2ea == 1)
+ {
+ if ((pPlayer->at90&1) == 0 && pXItem->state)
+ {
+ pPlayer->at90 |= 1;
+ pPlayer->at91[0] = pItem->index;
+ trTriggerSprite(pItem->index, pXItem, 0);
+ sprintf(buffer, "%s stole Blue Flag", gProfile[pPlayer->at57].name);
+ sndStartSample(8007, 255, 2, 0);
+ viewSetMessage(buffer);
+ }
+ }
+ if (pPlayer->at2ea == 0)
+ {
+ if ((pPlayer->at90&1) != 0 && !pXItem->state)
+ {
+ pPlayer->at90 &= ~1;
+ pPlayer->at91[0] = -1;
+ trTriggerSprite(pItem->index, pXItem, 1);
+ sprintf(buffer, "%s returned Blue Flag", gProfile[pPlayer->at57].name);
+ sndStartSample(8003, 255, 2, 0);
+ viewSetMessage(buffer);
+ }
+ if ((pPlayer->at90&2) != 0 && pXItem->state)
+ {
+ pPlayer->at90 &= ~2;
+ pPlayer->at91[1] = -1;
+ dword_21EFB0[pPlayer->at2ea] += 10;
+ dword_21EFD0[pPlayer->at2ea] += 240;
+ evSend(0, 0, 81, COMMAND_ID_1);
+ sprintf(buffer, "%s captured Red Flag!", gProfile[pPlayer->at57].name);
+ sndStartSample(8001, 255, 2, 0);
+ viewSetMessage(buffer);
+#if 0
+ if (dword_28E3D4 == 3 && myconnectindex == connecthead)
+ {
+ sprintf(buffer, "frag A killed B\n");
+ sub_7AC28(buffer);
+ }
+#endif
+ }
+ }
+ }
+ else if (pItem->type == 146)
+ {
+ if (pPlayer->at2ea == 0)
+ {
+ if((pPlayer->at90&2) == 0 && pXItem->state)
+ {
+ pPlayer->at90 |= 2;
+ pPlayer->at91[1] = pItem->index;
+ trTriggerSprite(pItem->index, pXItem, 0);
+ sprintf(buffer, "%s stole Red Flag", gProfile[pPlayer->at57].name);
+ sndStartSample(8006, 255, 2, 0);
+ viewSetMessage(buffer);
+ }
+ }
+ if (pPlayer->at2ea == 1)
+ {
+ if ((pPlayer->at90&2) != 0 && !pXItem->state)
+ {
+ pPlayer->at90 &= ~2;
+ pPlayer->at91[1] = -1;
+ trTriggerSprite(pItem->index, pXItem, 1);
+ sprintf(buffer, "%s returned Red Flag", gProfile[pPlayer->at57].name);
+ sndStartSample(8002, 255, 2, 0);
+ viewSetMessage(buffer);
+ }
+ if ((pPlayer->at90&1) != 0 && pXItem->state)
+ {
+ pPlayer->at90 &= ~1;
+ pPlayer->at91[0] = -1;
+ dword_21EFB0[pPlayer->at2ea] += 10;
+ dword_21EFD0[pPlayer->at2ea] += 240;
+ evSend(0, 0, 80, COMMAND_ID_1);
+ sprintf(buffer, "%s captured Red Flag!", gProfile[pPlayer->at57].name);
+ sndStartSample(8000, 255, 2, 0);
+ viewSetMessage(buffer);
+#if 0
+ if (dword_28E3D4 == 3 && myconnectindex == connecthead)
+ {
+ sprintf(buffer, "frag B killed A\n");
+ sub_7AC28(buffer);
+ }
+#endif
+ }
+ }
+ }
+ }
+ return 0;
+ case 147:
+ if (gGameOptions.nGameType != 3)
+ return 0;
+ evKill(pItem->index, 3, CALLBACK_ID_17);
+ pPlayer->at90 |= 1;
+ pPlayer->at91[0] = pItem->index;
+ break;
+ case 148:
+ if (gGameOptions.nGameType != 3)
+ return 0;
+ evKill(pItem->index, 3, CALLBACK_ID_17);
+ pPlayer->at90 |= 2;
+ pPlayer->at91[1] = pItem->index;
+ break;
+ case 140:
+ case 141:
+ case 142:
+ case 143:
+ case 144:
+ {
+ ARMORDATA *pArmorData = &armorData[pItem->type-140];
+ char va = 0;
+ if (pPlayer->at33e[1] < pArmorData->atc)
+ {
+ pPlayer->at33e[1] = ClipHigh(pPlayer->at33e[1]+pArmorData->at8, pArmorData->atc);
+ va = 1;
+ }
+ if (pPlayer->at33e[0] < pArmorData->at4)
+ {
+ pPlayer->at33e[0] = ClipHigh(pPlayer->at33e[0]+pArmorData->at0, pArmorData->at4);
+ va = 1;
+ }
+ if (pPlayer->at33e[2] < pArmorData->at14)
+ {
+ pPlayer->at33e[2] = ClipHigh(pPlayer->at33e[2]+pArmorData->at10, pArmorData->at14);
+ va = 1;
+ }
+ if (!va)
+ return 0;
+ pickupSnd = 779;
+ break;
+ }
+ case 121:
+ if (gGameOptions.nGameType == 0)
+ return 0;
+ if (!packAddItem(pPlayer, gItemData[nType].at8))
+ return 0;
+ break;
+ case 100:
+ case 101:
+ case 102:
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ if (pPlayer->at88[pItem->type-99])
+ return 0;
+ pPlayer->at88[pItem->type-99] = 1;
+ pickupSnd = 781;
+ break;
+ case 108:
+ case 109:
+ case 110:
+ case 111:
+ {
+ int addPower = gPowerUpInfo[nType].at3;
+ // by NoOne: allow custom amount for item
+ if (sprite[pItem->xvel].extra >= 0 && xsprite[sprite[pItem->xvel].extra].data1 > 0 && !VanillaMode() && !DemoRecordStatus())
+ addPower = xsprite[sprite[pItem->xvel].extra].data1;
+ if (!actHealDude(pXSprite, addPower, gPowerUpInfo[nType].at7))
+ return 0;
+ return 1;
+ }
+ case 107:
+ case 115:
+ case 118:
+ case 125:
+ if (!packAddItem(pPlayer, gItemData[nType].at8))
+ return 0;
+ break;
+ default:
+ if (!powerupActivate(pPlayer, nType))
+ return 0;
+ return 1;
+ }
+ sfxPlay3DSound(pSprite->x, pSprite->y, pSprite->z, pickupSnd, pSprite->sectnum);
+ return 1;
+}
+
+char PickupAmmo(PLAYER* pPlayer, spritetype* pAmmo)
+{
+ AMMOITEMDATA* pAmmoItemData = &gAmmoItemData[pAmmo->type - 60];
+ int nAmmoType = pAmmoItemData->ata;
+
+ if (pPlayer->at181[nAmmoType] >= gAmmoInfo[nAmmoType].at0) return 0;
+ else if (pAmmo->extra < 0 || xsprite[pAmmo->extra].data1 <= 0 || VanillaMode() || DemoRecordStatus())
+ pPlayer->at181[nAmmoType] = ClipHigh(pPlayer->at181[nAmmoType]+pAmmoItemData->at8, gAmmoInfo[nAmmoType].at0);
+ // by NoOne: allow custom amount for item
+ else
+ pPlayer->at181[nAmmoType] = ClipHigh(pPlayer->at181[nAmmoType] + xsprite[pAmmo->extra].data1, gAmmoInfo[nAmmoType].at0);
+
+ if (pAmmoItemData->atb)
+ pPlayer->atcb[pAmmoItemData->atb] = 1;
+ sfxPlay3DSound(pPlayer->pSprite, 782, -1, 0);
+ return 1;
+}
+
+char PickupWeapon(PLAYER *pPlayer, spritetype *pWeapon)
+{
+ WEAPONITEMDATA *pWeaponItemData = &gWeaponItemData[pWeapon->type-40];
+ int nWeaponType = pWeaponItemData->at8;
+ int nAmmoType = pWeaponItemData->ata;
+ if (!pPlayer->atcb[nWeaponType] || gGameOptions.nWeaponSettings == 2 || gGameOptions.nWeaponSettings == 3)
+ {
+ if (pWeapon->type == 50 && gGameOptions.nGameType > 1 && sub_3A158(pPlayer, NULL))
+ return 0;
+ pPlayer->atcb[nWeaponType] = 1;
+ if (nAmmoType == -1) return 0;
+ // By NoOne: allow to set custom ammo count for weapon pickups
+ if (pWeapon->extra < 0 || xsprite[pWeapon->extra].data1 <= 0 || VanillaMode() || DemoRecordStatus())
+ pPlayer->at181[nAmmoType] = ClipHigh(pPlayer->at181[nAmmoType] + pWeaponItemData->atc, gAmmoInfo[nAmmoType].at0);
+ else
+ pPlayer->at181[nAmmoType] = ClipHigh(pPlayer->at181[nAmmoType] + xsprite[pWeapon->extra].data1, gAmmoInfo[nAmmoType].at0);
+
+ int nNewWeapon = WeaponUpgrade(pPlayer, nWeaponType);
+ if (nNewWeapon != pPlayer->atbd)
+ {
+ pPlayer->atc3 = 0;
+ pPlayer->atbe = nNewWeapon;
+ }
+ sfxPlay3DSound(pPlayer->pSprite, 777, -1, 0);
+ return 1;
+ }
+ if (!actGetRespawnTime(pWeapon))
+ return 0;
+ if (nAmmoType == -1)
+ return 0;
+ if (pPlayer->at181[nAmmoType] >= gAmmoInfo[nAmmoType].at0)
+ return 0;
+ pPlayer->at181[nAmmoType] = ClipHigh(pPlayer->at181[nAmmoType]+pWeaponItemData->atc, gAmmoInfo[nAmmoType].at0);
+ sfxPlay3DSound(pPlayer->pSprite, 777, -1, 0);
+ return 1;
+}
+
+void PickUp(PLAYER *pPlayer, spritetype *pSprite)
+{
+ char buffer[80];
+ int nType = pSprite->type;
+ char pickedUp = 0;
+ int customMsg = -1;
+ if (nType != 40 && nType != 80) { // By NoOne: no pickup for random item generators.
+
+ XSPRITE* pXSprite = (pSprite->extra >= 0) ? &xsprite[pSprite->extra] : NULL;
+ if (pXSprite != NULL && pXSprite->txID != 3 && pXSprite->lockMsg > 0) // by NoOne: allow custom INI message instead "Picked up"
+ customMsg = pXSprite->lockMsg;
+
+ if (nType >= 100 && nType <= 149)
+ {
+ pickedUp = PickupItem(pPlayer, pSprite);
+ if (pickedUp && customMsg == -1) sprintf(buffer, "Picked up %s", gItemText[nType - 100]);
+ }
+ else if (nType >= 60 && nType < 81)
+ {
+ pickedUp = PickupAmmo(pPlayer, pSprite);
+ if (pickedUp && customMsg == -1) sprintf(buffer, "Picked up %s", gAmmoText[nType - 60]);
+ }
+ else if (nType >= 40 && nType < 51)
+ {
+ pickedUp = PickupWeapon(pPlayer, pSprite);
+ if (pickedUp && customMsg == -1) sprintf(buffer, "Picked up %s", gWeaponText[nType - 40]);
+ }
+ }
+ if (pickedUp)
+ {
+ if (pSprite->extra > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if (pXSprite->Pickup)
+ trTriggerSprite(pSprite->index, pXSprite, 32);
+ }
+ if (!actCheckRespawn(pSprite))
+ actPostSprite(pSprite->index, kStatFree);
+ pPlayer->at377 = 30;
+ if (pPlayer == gMe)
+ if (customMsg > 0) trTextOver(customMsg - 1);
+ else viewSetMessage(buffer);
+ }
+}
+
+void CheckPickUp(PLAYER *pPlayer)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ int x = pSprite->x;
+ int y = pSprite->y;
+ int z = pSprite->z;
+ int nSector = pSprite->sectnum;
+ int nNextSprite;
+ for (int nSprite = headspritestat[3]; nSprite >= 0; nSprite = nNextSprite)
+ {
+ spritetype *pItem = &sprite[nSprite];
+ nNextSprite = nextspritestat[nSprite];
+ if (pItem->hitag&32)
+ continue;
+ int dx = klabs(x-pItem->x)>>4;
+ if (dx > 48)
+ continue;
+ int dy = klabs(y-pItem->y)>>4;
+ if (dy > 48)
+ continue;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int vb = 0;
+ if (pItem->z < top)
+ vb = (top-pItem->z)>>8;
+ else if (pItem->z > bottom)
+ vb = (pItem->z-bottom)>>8;
+ if (vb > 32)
+ continue;
+ if (approxDist(dx,dy) > 48)
+ continue;
+ GetSpriteExtents(pItem, &top, &bottom);
+ if (cansee(x, y, z, nSector, pItem->x, pItem->y, pItem->z, pItem->sectnum)
+ || cansee(x, y, z, nSector, pItem->x, pItem->y, top, pItem->sectnum)
+ || cansee(x, y, z, nSector, pItem->x, pItem->y, bottom, pItem->sectnum))
+ PickUp(pPlayer, pItem);
+ }
+}
+
+int ActionScan(PLAYER *pPlayer, int *a2, int *a3)
+{
+ *a2 = 0;
+ *a3 = 0;
+ spritetype *pSprite = pPlayer->pSprite;
+ int x = Cos(pSprite->ang)>>16;
+ int y = Sin(pSprite->ang)>>16;
+ int z = pPlayer->at83;
+ int hit = HitScan(pSprite, pPlayer->at67, x, y, z, 0x10000040, 128);
+ int hitDist = approxDist(pSprite->x-gHitInfo.hitx, pSprite->y-gHitInfo.hity)>>4;
+ if (hitDist < 64)
+ {
+ switch (hit)
+ {
+ case 3:
+ *a2 = gHitInfo.hitsprite;
+ *a3 = sprite[*a2].extra;
+ if (*a3 > 0 && sprite[*a2].statnum == 4)
+ {
+ spritetype *pSprite = &sprite[*a2];
+ XSPRITE *pXSprite = &xsprite[*a3];
+ if (pSprite->type == 431)
+ {
+ if (gGameOptions.nGameType > 1 && sub_3A158(pPlayer, pSprite))
+ return -1;
+ pXSprite->data4 = pPlayer->at57;
+ pXSprite->isTriggered = 0;
+ }
+ }
+ if (*a3 > 0 && xsprite[*a3].Push)
+ return 3;
+ if (sprite[*a2].statnum == 6)
+ {
+ spritetype *pSprite = &sprite[*a2];
+ XSPRITE *pXSprite = &xsprite[*a3];
+ int nMass = dudeInfo[pSprite->type-kDudeBase].mass;
+ if (nMass)
+ {
+ int t2 = divscale(0xccccc, nMass, 8);
+ xvel[*a2] += mulscale16(x, t2);
+ yvel[*a2] += mulscale16(y, t2);
+ zvel[*a2] += mulscale16(z, t2);
+ }
+ if (pXSprite->Push && !pXSprite->state && !pXSprite->isTriggered)
+ trTriggerSprite(*a2, pXSprite, 30);
+ }
+ break;
+ case 0:
+ case 4:
+ *a2 = gHitInfo.hitwall;
+ *a3 = wall[*a2].extra;
+ if (*a3 > 0 && xwall[*a3].triggerPush)
+ return 0;
+ if (wall[*a2].nextsector >= 0)
+ {
+ *a2 = wall[*a2].nextsector;
+ *a3 = sector[*a2].extra;
+ if (*a3 > 0 && xsector[*a3].Wallpush)
+ return 6;
+ }
+ break;
+ case 1:
+ case 2:
+ *a2 = gHitInfo.hitsect;
+ *a3 = sector[*a2].extra;
+ if (*a3 > 0 && xsector[*a3].Push)
+ return 6;
+ break;
+ }
+ }
+ *a2 = pSprite->sectnum;
+ *a3 = sector[*a2].extra;
+ if (*a3 > 0 && xsector[*a3].Push)
+ return 6;
+ return -1;
+}
+
+void ProcessInput(PLAYER *pPlayer)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ int nSprite = pPlayer->at5b;
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][pPlayer->at2f];
+ GINPUT *pInput = &pPlayer->atc;
+ pPlayer->at2e = pInput->syncFlags.run;
+ if (pInput->buttonFlags.byte || pInput->forward || pInput->strafe || pInput->q16turn)
+ pPlayer->at30a = 0;
+ else if (pPlayer->at30a >= 0)
+ pPlayer->at30a += 4;
+ WeaponProcess(pPlayer);
+ if (pXSprite->health == 0)
+ {
+ char bSeqStat = playerSeqPlaying(pPlayer, 16);
+ if (pPlayer->at2ee != -1)
+ {
+ pPlayer->angold = pSprite->ang = getangle(sprite[pPlayer->at2ee].x - pSprite->x, sprite[pPlayer->at2ee].y - pSprite->y);
+ pPlayer->q16ang = fix16_from_int(pSprite->ang);
+ }
+ pPlayer->at1fe += 4;
+ if (!bSeqStat)
+ {
+ if (bVanilla)
+ pPlayer->q16horiz = fix16_from_int(mulscale16(0x8000-(Cos(ClipHigh(pPlayer->at1fe*8, 1024))>>15), 120));
+ else
+ pPlayer->q16horiz = mulscale16(0x8000-(Cos(ClipHigh(pPlayer->at1fe*8, 1024))>>15), F16(120));
+ }
+ if (pPlayer->atbd)
+ pInput->newWeapon = pPlayer->atbd;
+ if (pInput->keyFlags.action)
+ {
+ if (bSeqStat)
+ {
+ if (pPlayer->at1fe > 360)
+ seqSpawn(pPlayer->pDudeInfo->seqStartID+14, 3, pPlayer->pSprite->extra, nPlayerSurviveClient);
+ }
+ else if (seqGetStatus(3, pPlayer->pSprite->extra) < 0)
+ {
+ if (pPlayer->pSprite)
+ pPlayer->pSprite->type = 426;
+ actPostSprite(pPlayer->at5b, 4);
+ seqSpawn(pPlayer->pDudeInfo->seqStartID+15, 3, pPlayer->pSprite->extra, -1);
+ playerReset(pPlayer);
+ if (gGameOptions.nGameType == 0 && numplayers == 1)
+ {
+ if (gDemo.at0)
+ gDemo.Close();
+ pInput->keyFlags.restart = 1;
+ }
+ else
+ playerStart(pPlayer->at57);
+ }
+ pInput->keyFlags.action = 0;
+ }
+ return;
+ }
+ if (pPlayer->at2f == 1)
+ {
+ int x = Cos(pSprite->ang);
+ int y = Sin(pSprite->ang);
+ if (pInput->forward)
+ {
+ int forward = pInput->forward;
+ if (forward > 0)
+ forward = mulscale8(pPosture->at0, forward);
+ else
+ forward = mulscale8(pPosture->at8, forward);
+ xvel[nSprite] += mulscale30(forward, x);
+ yvel[nSprite] += mulscale30(forward, y);
+ }
+ if (pInput->strafe)
+ {
+ int strafe = pInput->strafe;
+ strafe = mulscale8(pPosture->at4, strafe);
+ xvel[nSprite] += mulscale30(strafe, y);
+ yvel[nSprite] -= mulscale30(strafe, x);
+ }
+ }
+ else if (pXSprite->height < 256)
+ {
+ int speed = 0x10000;
+ if (pXSprite->height > 0)
+ speed -= divscale16(pXSprite->height, 256);
+ int x = Cos(pSprite->ang);
+ int y = Sin(pSprite->ang);
+ if (pInput->forward)
+ {
+ int forward = pInput->forward;
+ if (forward > 0)
+ forward = mulscale8(pPosture->at0, forward);
+ else
+ forward = mulscale8(pPosture->at8, forward);
+ if (pXSprite->height)
+ forward = mulscale16(forward, speed);
+ xvel[nSprite] += mulscale30(forward, x);
+ yvel[nSprite] += mulscale30(forward, y);
+ }
+ if (pInput->strafe)
+ {
+ int strafe = pInput->strafe;
+ strafe = mulscale8(pPosture->at4, strafe);
+ if (pXSprite->height)
+ strafe = mulscale16(strafe, speed);
+ xvel[nSprite] += mulscale30(strafe, y);
+ yvel[nSprite] -= mulscale30(strafe, x);
+ }
+ }
+ if (pInput->q16turn)
+ pPlayer->q16ang = (pPlayer->q16ang+pInput->q16turn)&0x7ffffff;
+ if (pInput->keyFlags.spin180)
+ {
+ if (!pPlayer->at316)
+ pPlayer->at316 = -1024;
+ pInput->keyFlags.spin180 = 0;
+ }
+ if (pPlayer->at316 < 0)
+ {
+ int speed;
+ if (pPlayer->at2f == 1)
+ speed = 64;
+ else
+ speed = 128;
+ pPlayer->at316 = ClipLow(pPlayer->at316+speed, 0);
+ pPlayer->q16ang += fix16_from_int(speed);
+ }
+ pPlayer->q16ang = (pPlayer->q16ang+fix16_from_int(pSprite->ang-pPlayer->angold))&0x7ffffff;
+ pPlayer->angold = pSprite->ang = fix16_to_int(pPlayer->q16ang);
+ if (!pInput->buttonFlags.jump)
+ pPlayer->at31c = 0;
+
+ switch (pPlayer->at2f)
+ {
+ case 1:
+ if (pInput->buttonFlags.jump)
+ zvel[nSprite] -= 0x5b05;
+ if (pInput->buttonFlags.crouch)
+ zvel[nSprite] += 0x5b05;
+ break;
+ case 2:
+ if (!pInput->buttonFlags.crouch)
+ pPlayer->at2f = 0;
+ break;
+ default:
+ if (!pPlayer->at31c && pInput->buttonFlags.jump && pXSprite->height == 0)
+ {
+ sfxPlay3DSound(pSprite, 700, 0, 0);
+ if (packItemActive(pPlayer, 4))
+ zvel[nSprite] = -0x175555;
+ else
+ zvel[nSprite] = -0xbaaaa;
+
+
+ if (isShrinked(pPlayer->pSprite)) zvel[nSprite] -= -200000;
+ else if (isGrown(pPlayer->pSprite)) zvel[nSprite] += -250000;
+
+ pPlayer->at31c = 1;
+ }
+
+
+ if (pInput->buttonFlags.crouch)
+ pPlayer->at2f = 2;
+ break;
+ }
+ if (pInput->keyFlags.action)
+ {
+ int a2, a3;
+ int hit = ActionScan(pPlayer, &a2, &a3);
+ switch (hit)
+ {
+ case 6:
+ if (a3 > 0 && a3 <= 2048)
+ {
+ XSECTOR *pXSector = &xsector[a3];
+ int key = pXSector->Key;
+ if (pXSector->locked && pPlayer == gMe)
+ {
+ viewSetMessage("It's locked");
+ sndStartSample(3062, 255, 2, 0);
+ }
+ if (!key || pPlayer->at88[key])
+ trTriggerSector(a2, pXSector, 30);
+ else if (pPlayer == gMe)
+ {
+ viewSetMessage("That requires a key.");
+ sndStartSample(3063, 255, 2, 0);
+ }
+ }
+ break;
+ case 0:
+ {
+ XWALL *pXWall = &xwall[a3];
+ int key = pXWall->key;
+ if (pXWall->locked && pPlayer == gMe)
+ {
+ viewSetMessage("It's locked");
+ sndStartSample(3062, 255, 2, 0);
+ }
+ if (!key || pPlayer->at88[key])
+ trTriggerWall(a2, pXWall, 50);
+ else if (pPlayer == gMe)
+ {
+ viewSetMessage("That requires a key.");
+ sndStartSample(3063, 255, 2, 0);
+ }
+ break;
+ }
+ case 3:
+ {
+ XSPRITE *pXSprite = &xsprite[a3];
+ int key = pXSprite->key;
+ if (pXSprite->locked && pPlayer == gMe && pXSprite->lockMsg)
+ trTextOver(pXSprite->lockMsg);
+ if (!key || pPlayer->at88[key])
+ trTriggerSprite(a2, pXSprite, 30);
+ else if (pPlayer == gMe)
+ {
+ viewSetMessage("That requires a key.");
+ sndStartSample(3063, 255, 2, 0);
+ }
+ break;
+ }
+ }
+ if (pPlayer->at372 > 0)
+ pPlayer->at372 = ClipLow(pPlayer->at372-4*(6-gGameOptions.nDifficulty), 0);
+ if (pPlayer->at372 <= 0 && pPlayer->at376)
+ {
+ spritetype *pSprite2 = actSpawnDude(pPlayer->pSprite, 212, pPlayer->pSprite->clipdist<<1, 0);
+ pSprite2->ang = (pPlayer->pSprite->ang+1024)&2047;
+ int nSprite = pPlayer->pSprite->index;
+ int x = Cos(pPlayer->pSprite->ang)>>16;
+ int y = Sin(pPlayer->pSprite->ang)>>16;
+ xvel[pSprite2->index] = xvel[nSprite] + mulscale14(0x155555, x);
+ yvel[pSprite2->index] = yvel[nSprite] + mulscale14(0x155555, y);
+ zvel[pSprite2->index] = zvel[nSprite];
+ pPlayer->at376 = 0;
+ }
+ pInput->keyFlags.action = 0;
+ }
+ if (bVanilla)
+ {
+ if (pInput->keyFlags.lookCenter && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown)
+ {
+ if (pPlayer->q16look < 0)
+ pPlayer->q16look = fix16_min(pPlayer->q16look+F16(4), F16(0));
+ if (pPlayer->q16look > 0)
+ pPlayer->q16look = fix16_max(pPlayer->q16look-F16(4), F16(0));
+ if (!pPlayer->q16look)
+ pInput->keyFlags.lookCenter = 0;
+ }
+ else
+ {
+ if (pInput->buttonFlags.lookUp)
+ pPlayer->q16look = fix16_min(pPlayer->q16look+F16(4), F16(60));
+ if (pInput->buttonFlags.lookDown)
+ pPlayer->q16look = fix16_max(pPlayer->q16look-F16(4), F16(-60));
+ }
+ pPlayer->q16look = fix16_clamp(pPlayer->q16look+pInput->q16mlook, F16(-60), F16(60));
+ if (pPlayer->q16look > 0)
+ pPlayer->q16horiz = fix16_from_int(mulscale30(120, Sin(fix16_to_int(pPlayer->q16look)<<3)));
+ else if (pPlayer->q16look < 0)
+ pPlayer->q16horiz = fix16_from_int(mulscale30(180, Sin(fix16_to_int(pPlayer->q16look)<<3)));
+ else
+ pPlayer->q16horiz = 0;
+ }
+ else
+ {
+ CONSTEXPR int upAngle = 289;
+ CONSTEXPR int downAngle = -347;
+ CONSTEXPR double lookStepUp = 4.0*upAngle/60.0;
+ CONSTEXPR double lookStepDown = -4.0*downAngle/60.0;
+ if (pInput->keyFlags.lookCenter && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown)
+ {
+ if (pPlayer->q16look < 0)
+ pPlayer->q16look = fix16_min(pPlayer->q16look+F16(lookStepDown), F16(0));
+ if (pPlayer->q16look > 0)
+ pPlayer->q16look = fix16_max(pPlayer->q16look-F16(lookStepUp), F16(0));
+ if (!pPlayer->q16look)
+ pInput->keyFlags.lookCenter = 0;
+ }
+ else
+ {
+ if (pInput->buttonFlags.lookUp)
+ pPlayer->q16look = fix16_min(pPlayer->q16look+F16(lookStepUp), F16(upAngle));
+ if (pInput->buttonFlags.lookDown)
+ pPlayer->q16look = fix16_max(pPlayer->q16look-F16(lookStepDown), F16(downAngle));
+ }
+ pPlayer->q16look = fix16_clamp(pPlayer->q16look+(pInput->q16mlook<<3), F16(downAngle), F16(upAngle));
+ pPlayer->q16horiz = fix16_from_float(100.f*tanf(fix16_to_float(pPlayer->q16look)*fPI/1024.f));
+ }
+ int nSector = pSprite->sectnum;
+ int florhit = gSpriteHit[pSprite->extra].florhit & 0xe000;
+ char va;
+ if (pXSprite->height < 16 && (florhit == 0x4000 || florhit == 0))
+ va = 1;
+ else
+ va = 0;
+ if (va && (sector[nSector].floorstat&2))
+ {
+ int z1 = getflorzofslope(nSector, pSprite->x, pSprite->y);
+ int x2 = pSprite->x+mulscale30(64, Cos(pSprite->ang));
+ int y2 = pSprite->y+mulscale30(64, Sin(pSprite->ang));
+ short nSector2 = nSector;
+ updatesector(x2, y2, &nSector2);
+ if (nSector2 == nSector)
+ {
+ int z2 = getflorzofslope(nSector2, x2, y2);
+ pPlayer->q16slopehoriz = interpolate(pPlayer->q16slopehoriz, fix16_from_int(z1-z2)>>3, 0x4000);
+ }
+ }
+ else
+ {
+ pPlayer->q16slopehoriz = interpolate(pPlayer->q16slopehoriz, F16(0), 0x4000);
+ if (klabs(pPlayer->q16slopehoriz) < 4)
+ pPlayer->q16slopehoriz = 0;
+ }
+ pPlayer->at83 = (-fix16_to_int(pPlayer->q16horiz))<<7;
+ if (pInput->keyFlags.prevItem)
+ {
+ pInput->keyFlags.prevItem = 0;
+ packPrevItem(pPlayer);
+ }
+ if (pInput->keyFlags.nextItem)
+ {
+ pInput->keyFlags.nextItem = 0;
+ packNextItem(pPlayer);
+ }
+ if (pInput->keyFlags.useItem)
+ {
+ pInput->keyFlags.useItem = 0;
+ if (pPlayer->packInfo[pPlayer->at321].at1 > 0)
+ packUseItem(pPlayer, pPlayer->at321);
+ }
+ if (pInput->useFlags.useBeastVision)
+ {
+ pInput->useFlags.useBeastVision = 0;
+ if (pPlayer->packInfo[3].at1 > 0)
+ packUseItem(pPlayer, 3);
+ }
+ if (pInput->useFlags.useCrystalBall)
+ {
+ pInput->useFlags.useCrystalBall = 0;
+ if (pPlayer->packInfo[2].at1 > 0)
+ packUseItem(pPlayer, 2);
+ }
+ if (pInput->useFlags.useJumpBoots)
+ {
+ pInput->useFlags.useJumpBoots = 0;
+ if (pPlayer->packInfo[4].at1 > 0)
+ packUseItem(pPlayer, 4);
+ }
+ if (pInput->useFlags.useMedKit)
+ {
+ pInput->useFlags.useMedKit = 0;
+ if (pPlayer->packInfo[0].at1 > 0)
+ packUseItem(pPlayer, 0);
+ }
+ if (pInput->keyFlags.holsterWeapon)
+ {
+ pInput->keyFlags.holsterWeapon = 0;
+ if (pPlayer->atbd)
+ {
+ WeaponLower(pPlayer);
+ viewSetMessage("Holstering weapon");
+ }
+ }
+ CheckPickUp(pPlayer);
+}
+
+void playerProcess(PLAYER *pPlayer)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ int nSprite = pPlayer->at5b;
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][pPlayer->at2f];
+ powerupProcess(pPlayer);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int dzb = (bottom-pSprite->z)/4;
+ int dzt = (pSprite->z-top)/4;
+ int dw = pSprite->clipdist<<2;
+ if (!gNoClip)
+ {
+ short nSector = pSprite->sectnum;
+ if (pushmove_old(&pSprite->x, &pSprite->y, &pSprite->z, &nSector, dw, dzt, dzb, CLIPMASK0) == -1)
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, 500<<4);
+ if (pSprite->sectnum != nSector)
+ {
+ if (nSector == -1)
+ {
+ nSector = pSprite->sectnum;
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_0, 500<<4);
+ }
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ ChangeSpriteSect(nSprite, nSector);
+ }
+ }
+ ProcessInput(pPlayer);
+ int nSpeed = approxDist(xvel[nSprite], yvel[nSprite]);
+ pPlayer->at6b = interpolate(pPlayer->at6b, zvel[nSprite], 0x7000);
+ int dz = pPlayer->pSprite->z-pPosture->at24-pPlayer->at67;
+ if (dz > 0)
+ pPlayer->at6b += mulscale16(dz<<8, 0xa000);
+ else
+ pPlayer->at6b += mulscale16(dz<<8, 0x1800);
+ pPlayer->at67 += pPlayer->at6b>>8;
+ pPlayer->at73 = interpolate(pPlayer->at73, zvel[nSprite], 0x5000);
+ dz = pPlayer->pSprite->z-pPosture->at28-pPlayer->at6f;
+ if (dz > 0)
+ pPlayer->at73 += mulscale16(dz<<8, 0x8000);
+ else
+ pPlayer->at73 += mulscale16(dz<<8, 0xc00);
+ pPlayer->at6f += pPlayer->at73>>8;
+ pPlayer->at37 = ClipLow(pPlayer->at37-4, 0);
+ nSpeed >>= 16;
+ if (pPlayer->at2f == 1)
+ {
+ pPlayer->at3b = (pPlayer->at3b+17)&2047;
+ pPlayer->at4b = (pPlayer->at4b+17)&2047;
+ pPlayer->at3f = mulscale30(pPosture->at14*10, Sin(pPlayer->at3b*2));
+ pPlayer->at43 = mulscale30(pPosture->at18*pPlayer->at37, Sin(pPlayer->at3b-256));
+ pPlayer->at4f = mulscale30(pPosture->at1c*pPlayer->at37, Sin(pPlayer->at4b*2));
+ pPlayer->at53 = mulscale30(pPosture->at20*pPlayer->at37, Sin(pPlayer->at4b-0x155));
+ }
+ else
+ {
+ if (pXSprite->height < 256)
+ {
+ pPlayer->at3b = (pPlayer->at3b+pPosture->atc[pPlayer->at2e]*4) & 2047;
+ pPlayer->at4b = (pPlayer->at4b+(pPosture->atc[pPlayer->at2e]*4)/2) & 2047;
+ if (pPlayer->at2e)
+ {
+ if (pPlayer->at37 < 60)
+ pPlayer->at37 = ClipHigh(pPlayer->at37+nSpeed, 60);
+ }
+ else
+ {
+ if (pPlayer->at37 < 30)
+ pPlayer->at37 = ClipHigh(pPlayer->at37+nSpeed, 30);
+ }
+ }
+ pPlayer->at3f = mulscale30(pPosture->at14*pPlayer->at37, Sin(pPlayer->at3b*2));
+ pPlayer->at43 = mulscale30(pPosture->at18*pPlayer->at37, Sin(pPlayer->at3b-256));
+ pPlayer->at4f = mulscale30(pPosture->at1c*pPlayer->at37, Sin(pPlayer->at4b*2));
+ pPlayer->at53 = mulscale30(pPosture->at20*pPlayer->at37, Sin(pPlayer->at4b-0x155));
+ }
+ pPlayer->at35a = 0;
+ pPlayer->at37f = ClipLow(pPlayer->at37f-4, 0);
+ pPlayer->at35e = ClipLow(pPlayer->at35e-4, 0);
+ pPlayer->at362 = ClipLow(pPlayer->at362-4, 0);
+ pPlayer->at366 = ClipLow(pPlayer->at366-4, 0);
+ pPlayer->at36a = ClipLow(pPlayer->at36a-4, 0);
+ pPlayer->at377 = ClipLow(pPlayer->at377-4, 0);
+ if (pPlayer == gMe && gMe->pXSprite->health == 0)
+ pPlayer->at376 = 0;
+ if (!pXSprite->health)
+ return;
+ pPlayer->at87 = 0;
+ if (pPlayer->at2f == 1)
+ {
+ pPlayer->at87 = 1;
+ int nSector = pSprite->sectnum;
+ int nLink = gLowerLink[nSector];
+ if (nLink > 0 && (sprite[nLink].type == 14 || sprite[nLink].type == 10))
+ {
+ if (getceilzofslope(nSector, pSprite->x, pSprite->y) > pPlayer->at67)
+ pPlayer->at87 = 0;
+ }
+ }
+ if (!pPlayer->at87)
+ {
+ pPlayer->at2f2 = 1200;
+ pPlayer->at36e = 0;
+ if (packItemActive(pPlayer, 1))
+ packUseItem(pPlayer, 1);
+ }
+ int nType = kDudePlayer1-kDudeBase;
+ switch (pPlayer->at2f)
+ {
+ case 1:
+ seqSpawn(dudeInfo[nType].seqStartID+9, 3, nXSprite, -1);
+ break;
+ case 2:
+ seqSpawn(dudeInfo[nType].seqStartID+10, 3, nXSprite, -1);
+ break;
+ default:
+ if (!nSpeed)
+ seqSpawn(dudeInfo[nType].seqStartID, 3, nXSprite, -1);
+ else
+ seqSpawn(dudeInfo[nType].seqStartID+8, 3, nXSprite, -1);
+ break;
+ }
+}
+
+spritetype *playerFireMissile(PLAYER *pPlayer, int a2, int a3, int a4, int a5, int a6)
+{
+ return actFireMissile(pPlayer->pSprite, a2, pPlayer->at6f-pPlayer->pSprite->z, a3, a4, a5, a6);
+}
+
+spritetype * playerFireThing(PLAYER *pPlayer, int a2, int a3, int thingType, int a5)
+{
+ dassert(thingType >= kThingBase && thingType < kThingMax);
+ return actFireThing(pPlayer->pSprite, a2, pPlayer->at6f-pPlayer->pSprite->z, pPlayer->at83+a3, thingType, a5);
+}
+
+void playerFrag(PLAYER *pKiller, PLAYER *pVictim)
+{
+ dassert(pKiller != NULL);
+ dassert(pVictim != NULL);
+
+ char buffer[128] = "";
+ int nKiller = pKiller->pSprite->type-kDudePlayer1;
+ dassert(nKiller >= 0 && nKiller < kMaxPlayers);
+ int nVictim = pVictim->pSprite->type-kDudePlayer1;
+ dassert(nVictim >= 0 && nVictim < kMaxPlayers);
+ if (myconnectindex == connecthead)
+ {
+ sprintf(buffer, "frag %d killed %d\n", pKiller->at57+1, pVictim->at57+1);
+ sub_7AC28(buffer);
+ buffer[0] = 0;
+ }
+ if (nKiller == nVictim)
+ {
+ pVictim->at2ee = -1;
+ pVictim->at2c6--;
+ pVictim->at2ca[nVictim]--;
+ if (gGameOptions.nGameType == 3)
+ dword_21EFB0[pVictim->at2ea]--;
+ int nMessage = Random(5);
+ int nSound = gSuicide[nMessage].at4;
+ if (pVictim == gMe && gMe->at372 <= 0)
+ {
+ sprintf(buffer, "You killed yourself!");
+ if (gGameOptions.nGameType > 0 && nSound >= 0)
+ sndStartSample(nSound, 255, 2, 0);
+ }
+ }
+ else
+ {
+ pKiller->at2c6++;
+ pKiller->at2ca[nKiller]++;
+ if (gGameOptions.nGameType == 3)
+ {
+ if (pKiller->at2ea == pVictim->at2ea)
+ dword_21EFB0[pKiller->at2ea]--;
+ else
+ {
+ dword_21EFB0[pKiller->at2ea]++;
+ dword_21EFD0[pKiller->at2ea]+=120;
+ }
+ }
+ int nMessage = Random(25);
+ int nSound = gVictory[nMessage].at4;
+ const char* pzMessage = gVictory[nMessage].at0;
+ sprintf(buffer, pzMessage, gProfile[nKiller].name, gProfile[nVictim].name);
+ if (gGameOptions.nGameType > 0 && nSound >= 0 && pKiller == gMe)
+ sndStartSample(nSound, 255, 2, 0);
+ }
+ viewSetMessage(buffer);
+}
+
+void FragPlayer(PLAYER *pPlayer, int nSprite)
+{
+ spritetype *pSprite = NULL;
+ if (nSprite >= 0)
+ pSprite = &sprite[nSprite];
+ if (pSprite && IsPlayerSprite(pSprite))
+ {
+ PLAYER *pKiller = &gPlayer[pSprite->type-kDudePlayer1];
+ playerFrag(pKiller, pPlayer);
+ int nTeam1 = pKiller->at2ea&1;
+ int nTeam2 = pPlayer->at2ea&1;
+ if (nTeam1 == 0)
+ {
+ if (nTeam1 != nTeam2)
+ evSend(0, 0, 15, COMMAND_ID_3);
+ else
+ evSend(0, 0, 16, COMMAND_ID_3);
+ }
+ else
+ {
+ if (nTeam1 == nTeam2)
+ evSend(0, 0, 16, COMMAND_ID_3);
+ else
+ evSend(0, 0, 15, COMMAND_ID_3);
+ }
+ }
+}
+
+int playerDamageArmor(PLAYER *pPlayer, DAMAGE_TYPE nType, int nDamage)
+{
+ DAMAGEINFO *pDamageInfo = &damageInfo[nType];
+ int nArmorType = pDamageInfo->at0;
+ if (nArmorType >= 0 && pPlayer->at33e[nArmorType])
+ {
+#if 0
+ int vbp = (nDamage*7)/8-nDamage/4;
+ int v8 = pPlayer->at33e[nArmorType];
+ int t = nDamage/4 + vbp * v8 / 3200;
+ v8 -= t;
+#endif
+ int v8 = pPlayer->at33e[nArmorType];
+ int t = scale(v8, 0, 3200, nDamage/4, (nDamage*7)/8);
+ v8 -= t;
+ nDamage -= t;
+ pPlayer->at33e[nArmorType] = ClipLow(v8, 0);
+ }
+ return nDamage;
+}
+
+spritetype *sub_40A94(PLAYER *pPlayer, int a2)
+{
+ char buffer[80];
+ spritetype *pSprite = NULL;
+ switch (a2)
+ {
+ case 147:
+ pPlayer->at90 &= ~1;
+ pSprite = actDropObject(pPlayer->pSprite, 147);
+ if (pSprite)
+ pSprite->owner = pPlayer->at91[0];
+ sprintf(buffer, "%s dropped Blue Flag", gProfile[pPlayer->at57].name);
+ sndStartSample(8005, 255, 2, 0);
+ viewSetMessage(buffer);
+ break;
+ case 148:
+ pPlayer->at90 &= ~2;
+ pSprite = actDropObject(pPlayer->pSprite, 148);
+ if (pSprite)
+ pSprite->owner = pPlayer->at91[1];
+ sprintf(buffer, "%s dropped Red Flag", gProfile[pPlayer->at57].name);
+ sndStartSample(8004, 255, 2, 0);
+ viewSetMessage(buffer);
+ break;
+ }
+ return pSprite;
+}
+
+int playerDamageSprite(int nSource, PLAYER *pPlayer, DAMAGE_TYPE nDamageType, int nDamage)
+{
+ dassert(nSource < kMaxSprites);
+ dassert(pPlayer != NULL);
+ if (pPlayer->ata1[nDamageType])
+ return 0;
+ nDamage = playerDamageArmor(pPlayer, nDamageType, nDamage);
+ pPlayer->at366 = ClipHigh(pPlayer->at366+(nDamage>>3), 600);
+
+ spritetype *pSprite = pPlayer->pSprite;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ int nXSprite = pSprite->extra;
+ int nXSector = sector[pSprite->sectnum].extra;
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int nDeathSeqID = -1;
+ int v18 = -1;
+ int nSprite = pSprite->index;
+ char va = playerSeqPlaying(pPlayer, 16);
+ if (!pXSprite->health)
+ {
+ if (va)
+ {
+ switch (nDamageType)
+ {
+ case DAMAGE_TYPE_5:
+ nDeathSeqID = 18;
+ sfxPlay3DSound(pSprite, 716, 0, 0);
+ break;
+ case DAMAGE_TYPE_3:
+ GibSprite(pSprite, GIBTYPE_7, NULL, NULL);
+ GibSprite(pSprite, GIBTYPE_15, NULL, NULL);
+ pPlayer->pSprite->cstat |= 32768;
+ nDeathSeqID = 17;
+ break;
+ default:
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ CGibPosition gibPos(pSprite->x, pSprite->y, top);
+ CGibVelocity gibVel(xvel[pSprite->index]>>1, yvel[pSprite->index]>>1, -0xccccc);
+ GibSprite(pSprite, GIBTYPE_27, &gibPos, &gibVel);
+ GibSprite(pSprite, GIBTYPE_7, NULL, NULL);
+ fxSpawnBlood(pSprite, nDamage<<4);
+ fxSpawnBlood(pSprite, nDamage<<4);
+ nDeathSeqID = 17;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ int nHealth = pXSprite->health-nDamage;
+ pXSprite->health = ClipLow(nHealth, 0);
+ if (pXSprite->health > 0 && pXSprite->health < 16)
+ {
+ nDamageType = DAMAGE_TYPE_2;
+ pXSprite->health = 0;
+ nHealth = -25;
+ }
+ if (pXSprite->health > 0)
+ {
+ DAMAGEINFO *pDamageInfo = &damageInfo[nDamageType];
+ int nSound;
+ if (nDamage >= (10<<4))
+ nSound = pDamageInfo->at4[0];
+ else
+ nSound = pDamageInfo->at4[Random(3)];
+ if (nDamageType == DAMAGE_TYPE_4 && pXSprite->medium == 1 && !pPlayer->at376)
+ nSound = 714;
+ sfxPlay3DSound(pSprite, nSound, 0, 6);
+ return nDamage;
+ }
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ if (gGameOptions.nGameType == 3 && pPlayer->at90)
+ {
+ if (pPlayer->at90&1)
+ sub_40A94(pPlayer, 147);
+ if (pPlayer->at90&2)
+ sub_40A94(pPlayer, 148);
+ }
+ pPlayer->at1fe = 0;
+ pPlayer->at1b1 = 0;
+ pPlayer->atbd = 0;
+ pPlayer->at2ee = nSource;
+ pPlayer->at34e = 0;
+ if (nDamageType == DAMAGE_TYPE_3 && nDamage < (9<<4))
+ nDamageType = DAMAGE_TYPE_0;
+ switch (nDamageType)
+ {
+ case DAMAGE_TYPE_3:
+ sfxPlay3DSound(pSprite, 717, 0, 0);
+ GibSprite(pSprite, GIBTYPE_7, NULL, NULL);
+ GibSprite(pSprite, GIBTYPE_15, NULL, NULL);
+ pPlayer->pSprite->cstat |= 32768;
+ nDeathSeqID = 2;
+ break;
+ case DAMAGE_TYPE_1:
+ sfxPlay3DSound(pSprite, 718, 0, 0);
+ nDeathSeqID = 3;
+ break;
+ case DAMAGE_TYPE_4:
+ nDeathSeqID = 1;
+ break;
+ default:
+ if (nHealth < -20 && gGameOptions.nGameType >= 2 && Chance(0x4000))
+ {
+ DAMAGEINFO *pDamageInfo = &damageInfo[nDamageType];
+ sfxPlay3DSound(pSprite, pDamageInfo->at10[0], 0, 2);
+ nDeathSeqID = 16;
+ v18 = nPlayerKeelClient;
+ powerupActivate(pPlayer, 28);
+ pXSprite->target = nSource;
+ evPost(pSprite->index, 3, 15, CALLBACK_ID_13);
+ }
+ else
+ {
+ sfxPlay3DSound(pSprite, 716, 0, 0);
+ nDeathSeqID = 1;
+ }
+ break;
+ }
+ }
+ if (nDeathSeqID < 0)
+ return nDamage;
+ if (nDeathSeqID != 16)
+ {
+ powerupClear(pPlayer);
+ if (nXSector > 0 && xsector[nXSector].Exit)
+ trTriggerSector(pSprite->sectnum, &xsector[nXSector], 43);
+ pSprite->hitag |= 7;
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (gPlayer[p].at2ee == nSprite && gPlayer[p].at1fe > 0)
+ gPlayer[p].at2ee = -1;
+ }
+ FragPlayer(pPlayer, nSource);
+ trTriggerSprite(nSprite, pXSprite, 0);
+ }
+ dassert(gSysRes.Lookup(pDudeInfo->seqStartID + nDeathSeqID, "SEQ") != NULL);
+ seqSpawn(pDudeInfo->seqStartID+nDeathSeqID, 3, nXSprite, v18);
+ return nDamage;
+}
+
+int UseAmmo(PLAYER *pPlayer, int nAmmoType, int nDec)
+{
+ if (gInfiniteAmmo)
+ return 9999;
+ if (nAmmoType == -1)
+ return 9999;
+ pPlayer->at181[nAmmoType] = ClipLow(pPlayer->at181[nAmmoType]-nDec, 0);
+ return pPlayer->at181[nAmmoType];
+}
+
+void sub_41250(PLAYER *pPlayer)
+{
+ int v4 = pPlayer->at1be.dz;
+ int dz = pPlayer->at6f-pPlayer->pSprite->z;
+ if (UseAmmo(pPlayer, 9, 0) < 8)
+ {
+ pPlayer->at34e = 0;
+ return;
+ }
+ for (int i = 0; i < 4; i++)
+ {
+ int ang1 = (pPlayer->at352+pPlayer->at356)&2047;
+ actFireVector(pPlayer->pSprite, 0, dz, Cos(ang1)>>16, Sin(ang1)>>16, v4, VECTOR_TYPE_21);
+ int ang2 = (pPlayer->at352+2048-pPlayer->at356)&2047;
+ actFireVector(pPlayer->pSprite, 0, dz, Cos(ang2)>>16, Sin(ang2)>>16, v4, VECTOR_TYPE_21);
+ }
+ pPlayer->at34e = ClipLow(pPlayer->at34e-1, 0);
+}
+
+void playerLandingSound(PLAYER *pPlayer)
+{
+ static int surfaceSound[] = {
+ -1,
+ 600,
+ 601,
+ 602,
+ 603,
+ 604,
+ 605,
+ 605,
+ 605,
+ 600,
+ 605,
+ 605,
+ 605,
+ 604,
+ 603
+ };
+ spritetype *pSprite = pPlayer->pSprite;
+ SPRITEHIT *pHit = &gSpriteHit[pSprite->extra];
+ if (pHit->florhit)
+ {
+ char nSurf = tileGetSurfType(pHit->florhit);
+ if (nSurf)
+ sfxPlay3DSound(pSprite, surfaceSound[nSurf], -1, 0);
+ }
+}
+
+void PlayerSurvive(int, int nXSprite)
+{
+ char buffer[80];
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ actHealDude(pXSprite, 1, 2);
+ if (gGameOptions.nGameType > 0 && numplayers > 1)
+ {
+ sfxPlay3DSound(pSprite, 3009, 0, 6);
+ if (IsPlayerSprite(pSprite))
+ {
+ PLAYER *pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ if (pPlayer == gMe)
+ viewSetMessage("I LIVE...AGAIN!!");
+ else
+ {
+ sprintf(buffer, "%s lives again!", gProfile[pPlayer->at57].name);
+ viewSetMessage(buffer);
+ }
+ pPlayer->atc.newWeapon = 1;
+ }
+ }
+}
+
+void PlayerKeelsOver(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ if (gPlayer[p].pXSprite == pXSprite)
+ {
+ PLAYER *pPlayer = &gPlayer[p];
+ playerDamageSprite(pPlayer->at2ee, pPlayer, DAMAGE_TYPE_5, 500<<4);
+ return;
+ }
+ }
+}
+
+class PlayerLoadSave : public LoadSave
+{
+public:
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+void PlayerLoadSave::Load(void)
+{
+ Read(dword_21EFB0, sizeof(dword_21EFB0));
+ Read(&gNetPlayers, sizeof(gNetPlayers));
+ Read(&gProfile, sizeof(gProfile));
+ Read(&gPlayer, sizeof(gPlayer));
+ for (int i = 0; i < gNetPlayers; i++)
+ {
+ gPlayer[i].pSprite = &sprite[gPlayer[i].at5b];
+ gPlayer[i].pXSprite = &xsprite[gPlayer[i].pSprite->extra];
+ gPlayer[i].pDudeInfo = &dudeInfo[gPlayer[i].pSprite->type-kDudeBase];
+ }
+}
+
+void PlayerLoadSave::Save(void)
+{
+ Write(dword_21EFB0, sizeof(dword_21EFB0));
+ Write(&gNetPlayers, sizeof(gNetPlayers));
+ Write(&gProfile, sizeof(gProfile));
+ Write(&gPlayer, sizeof(gPlayer));
+}
+
+static PlayerLoadSave *myLoadSave;
+
+void PlayerLoadSaveConstruct(void)
+{
+ myLoadSave = new PlayerLoadSave();
+}
diff --git a/source/blood/src/player.h b/source/blood/src/player.h
new file mode 100644
index 000000000..eaac430c4
--- /dev/null
+++ b/source/blood/src/player.h
@@ -0,0 +1,252 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "fix16.h"
+#include "common_game.h"
+#include "actor.h"
+#include "blood.h"
+#include "config.h"
+#include "controls.h"
+#include "db.h"
+#include "dude.h"
+
+enum LifeMode {
+ kModeHuman = 0,
+ kModeBeast,
+ kModeHumanShrink,
+ kModeHumanGrown,
+};
+
+struct PACKINFO {
+ char at0; // is active (0/1)
+ int at1 = 0; // remaining percent
+};
+
+struct PLAYER {
+ spritetype *pSprite;
+ XSPRITE *pXSprite;
+ DUDEINFO *pDudeInfo;
+ GINPUT atc;
+ //short atc; // INPUT
+ //char at10; // forward
+ //short at11; // turn
+ //char hearDist; // strafe
+ //int at14; // buttonFlags
+ //unsigned int at18; // keyFlags
+ //char at1c; // useFlags;
+ //char at20; // newWeapon
+ //char at21; // mlook
+ int at22;
+ int at26; // weapon qav
+ int at2a; // qav callback
+ char at2e; // run
+ int at2f; // state
+ int at33; // unused?
+ int at37;
+ int at3b;
+ int at3f; // bob height
+ int at43; // bob width
+ int at47;
+ int at4b;
+ int at4f; // bob sway y
+ int at53; // bob sway x
+ int at57; // Connect id
+ int at5b; // spritenum
+ int at5f; // life mode
+ int at63;
+ int at67; // view z
+ int at6b;
+ int at6f; // weapon z
+ int at73;
+ fix16_t q16look;
+ int q16horiz; // horiz
+ int q16slopehoriz; // horizoff
+ int at83;
+ char at87; // underwater
+ char at88[8]; // keys
+ char at90; // flag capture
+ short at91[8];
+ int ata1[7];
+ char atbd; // weapon
+ char atbe; // pending weapon
+ int atbf, atc3, atc7;
+ char atcb[14]; // hasweapon
+ int atd9[14];
+ int at111[2][14];
+ //int at149[14];
+ int at181[12]; // ammo
+ char at1b1;
+ int at1b2;
+ int at1b6;
+ int at1ba;
+ Aim at1be; // world
+ //int at1c6;
+ Aim at1ca; // relative
+ //int at1ca;
+ //int at1ce;
+ //int at1d2;
+ int at1d6; // aim target sprite
+ int at1da;
+ short at1de[16];
+ int at1fe;
+ int at202[kMaxPowerUps]; // [13]: cloak of invisibility, [14]: death mask (invulnerability), [15]: jump boots, [17]: guns akimbo, [18]: diving suit, [21]: crystal ball, [24]: reflective shots, [25]: beast vision, [26]: cloak of shadow
+ int at2c6; // frags
+ int at2ca[8];
+ int at2ea;
+ int at2ee; // killer
+ int at2f2;
+ int at2f6;
+ int at2fa;
+ int at2fe;
+ int at302;
+ int at306;
+ int at30a;
+ int at30e;
+ int at312;
+ int at316;
+ char at31a; // God mode
+ char at31b; // Fall scream
+ char at31c;
+ int at31d; // pack timer
+ int at321; // pack id 1: diving suit, 2: crystal ball, 3: beast vision 4: jump boots
+ PACKINFO packInfo[5]; // at325 [1]: diving suit, [2]: crystal ball, [3]: beast vision [4]: jump boots
+ int at33e[3]; // armor
+ //int at342;
+ //int at346;
+ int voodooTarget; // at34a
+ int at34e;
+ int at352;
+ int at356;
+ int at35a; // quake
+ int at35e;
+ int at362; // light
+ int at366;
+ int at36a; // blind
+ int at36e; // choke
+ int at372;
+ char at376; // hand
+ int at377;
+ char at37b; // weapon flash
+ int at37f; // quake2
+ fix16_t q16ang;
+ int angold;
+ int player_par;
+ int nWaterPal;
+};
+
+struct POSTURE {
+ int at0;
+ int at4;
+ int at8;
+ int atc[2];
+ int at14;
+ int at18;
+ int at1c;
+ int at20;
+ int at24;
+ int at28;
+ int at2c;
+ int at30;
+};
+
+struct PROFILE {
+ int nAutoAim;
+ int nWeaponSwitch;
+ int skill;
+ char name[MAXPLAYERNAME];
+};
+
+struct AMMOINFO {
+ int at0;
+ signed char at4;
+};
+
+struct POWERUPINFO
+{
+ short at0;
+ char at2;
+ int at3; // max value
+ int at7;
+};
+
+extern POSTURE gPosture[4][3];
+
+extern PLAYER gPlayer[kMaxPlayers];
+extern PLAYER *gMe, *gView;
+
+extern PROFILE gProfile[kMaxPlayers];
+
+extern int dword_21EFB0[kMaxPlayers];
+extern int dword_21EFD0[kMaxPlayers];
+extern AMMOINFO gAmmoInfo[];
+extern POWERUPINFO gPowerUpInfo[kMaxPowerUps];
+
+int powerupCheck(PLAYER *pPlayer, int nPowerUp);
+char powerupActivate(PLAYER *pPlayer, int nPowerUp);
+void powerupDeactivate(PLAYER *pPlayer, int nPowerUp);
+void powerupSetState(PLAYER *pPlayer, int nPowerUp, char bState);
+void powerupProcess(PLAYER *pPlayer);
+void powerupClear(PLAYER *pPlayer);
+void powerupInit(void);
+int packItemToPowerup(int nPack);
+int powerupToPackItem(int nPowerUp);
+char packAddItem(PLAYER *pPlayer, unsigned int nPack);
+int packCheckItem(PLAYER *pPlayer, int nPack);
+char packItemActive(PLAYER *pPlayer, int nPack);
+void packUseItem(PLAYER *pPlayer, int nPack);
+void packPrevItem(PLAYER *pPlayer);
+void packNextItem(PLAYER *pPlayer);
+char playerSeqPlaying(PLAYER * pPlayer, int nSeq);
+void playerSetRace(PLAYER *pPlayer, int nLifeMode);
+void playerSetGodMode(PLAYER *pPlayer, char bGodMode);
+void playerResetInertia(PLAYER *pPlayer);
+void playerStart(int nPlayer);
+void playerReset(PLAYER *pPlayer);
+void playerInit(int nPlayer, unsigned int a2);
+char sub_3A158(PLAYER *a1, spritetype *a2);
+char PickupItem(PLAYER *pPlayer, spritetype *pItem);
+char PickupAmmo(PLAYER *pPlayer, spritetype *pAmmo);
+char PickupWeapon(PLAYER *pPlayer, spritetype *pWeapon);
+void PickUp(PLAYER *pPlayer, spritetype *pSprite);
+void CheckPickUp(PLAYER *pPlayer);
+int ActionScan(PLAYER *pPlayer, int *a2, int *a3);
+void ProcessInput(PLAYER *pPlayer);
+void playerProcess(PLAYER *pPlayer);
+spritetype *playerFireMissile(PLAYER *pPlayer, int a2, int a3, int a4, int a5, int a6);
+spritetype *playerFireThing(PLAYER *pPlayer, int a2, int a3, int thingType, int a5);
+void playerFrag(PLAYER *pKiller, PLAYER *pVictim);
+void FragPlayer(PLAYER *pPlayer, int nSprite);
+int playerDamageArmor(PLAYER *pPlayer, DAMAGE_TYPE nType, int nDamage);
+spritetype *sub_40A94(PLAYER *pPlayer, int a2);
+int playerDamageSprite(int nSource, PLAYER *pPlayer, DAMAGE_TYPE nDamageType, int nDamage);
+int UseAmmo(PLAYER *pPlayer, int nAmmoType, int nDec);
+void sub_41250(PLAYER *pPlayer);
+void playerLandingSound(PLAYER *pPlayer);
+void PlayerSurvive(int, int nXSprite);
+void PlayerKeelsOver(int, int nXSprite);
+bool isGrown(spritetype* pSprite);
+bool isShrinked(spritetype* pSprite);
+bool shrinkPlayerSize(PLAYER* pPlayer, int divider);
+bool growPlayerSize(PLAYER* pPlayer, int multiplier);
+bool resetPlayerSize(PLAYER* pPlayer);
+void deactivateSizeShrooms(PLAYER* pPlayer);
diff --git a/source/blood/src/pqueue.cpp b/source/blood/src/pqueue.cpp
new file mode 100644
index 000000000..f411f1215
--- /dev/null
+++ b/source/blood/src/pqueue.cpp
@@ -0,0 +1,147 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "common_game.h"
+#include "pqueue.h"
+
+PriorityQueue::~PriorityQueue()
+{
+}
+
+VanillaPriorityQueue::~VanillaPriorityQueue()
+{
+}
+
+void VanillaPriorityQueue::Clear(void)
+{
+ fNodeCount = 0;
+ memset(queueItems, 0, sizeof(queueItems));
+}
+
+void VanillaPriorityQueue::Upheap(void)
+{
+ queueItem item = queueItems[fNodeCount];
+ queueItems[0].at0 = 0;
+ unsigned int x = fNodeCount;
+ while (queueItems[x>>1] > item)
+ {
+ queueItems[x] = queueItems[x>>1];
+ x >>= 1;
+ }
+ queueItems[x] = item;
+}
+
+void VanillaPriorityQueue::Downheap(unsigned int n)
+{
+ queueItem item = queueItems[n];
+ while (fNodeCount/2 >= n)
+ {
+ unsigned int t = n*2;
+ if (t < fNodeCount && queueItems[t] > queueItems[t+1])
+ t++;
+ if (item <= queueItems[t])
+ break;
+ queueItems[n] = queueItems[t];
+ n = t;
+ }
+ queueItems[n] = item;
+}
+
+void VanillaPriorityQueue::Delete(unsigned int k)
+{
+ dassert(k <= fNodeCount);
+ queueItems[k] = queueItems[fNodeCount--];
+ Downheap(k);
+}
+
+void VanillaPriorityQueue::Insert(unsigned int a1, unsigned int a2)
+{
+ dassert(fNodeCount < kPQueueSize);
+ fNodeCount++;
+ queueItems[fNodeCount].at0 = a1;
+ queueItems[fNodeCount].at4 = a2;
+ Upheap();
+}
+
+unsigned int VanillaPriorityQueue::Remove(void)
+{
+ unsigned int data = queueItems[1].at4;
+ queueItems[1] = queueItems[fNodeCount--];
+ Downheap(1);
+ return data;
+}
+
+unsigned int VanillaPriorityQueue::LowestPriority(void)
+{
+ dassert(fNodeCount > 0);
+ return queueItems[1].at0;
+}
+
+void VanillaPriorityQueue::Kill(std::function pMatch)
+{
+ for (unsigned int i = 1; i <= fNodeCount;)
+ {
+ if (pMatch(queueItems[i].at4))
+ Delete(i);
+ else
+ i++;
+ }
+}
+
+StdPriorityQueue::~StdPriorityQueue()
+{
+ stdQueue.clear();
+}
+
+void StdPriorityQueue::Clear(void)
+{
+ stdQueue.clear();
+}
+
+void StdPriorityQueue::Insert(unsigned int nPriority, unsigned int nData)
+{
+ stdQueue.insert({ nPriority, nData });
+}
+
+unsigned int StdPriorityQueue::Remove(void)
+{
+ dassert(stdQueue.size() > 0);
+ int nData = stdQueue.begin()->at4;
+ stdQueue.erase(stdQueue.begin());
+ return nData;
+}
+
+unsigned int StdPriorityQueue::LowestPriority(void)
+{
+ return stdQueue.begin()->at0;
+}
+
+void StdPriorityQueue::Kill(std::function pMatch)
+{
+ for (auto i = stdQueue.begin(); i != stdQueue.end();)
+ {
+ if (pMatch(i->at4))
+ i = stdQueue.erase(i);
+ else
+ i++;
+ }
+}
diff --git a/source/blood/src/pqueue.h b/source/blood/src/pqueue.h
new file mode 100644
index 000000000..6979e575f
--- /dev/null
+++ b/source/blood/src/pqueue.h
@@ -0,0 +1,98 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include
+#include
+#define kPQueueSize 1024
+
+struct queueItem
+{
+ unsigned int at0; // priority
+ unsigned int at4; // data
+ bool operator>(const queueItem& other) const
+ {
+ return at0 > other.at0;
+ }
+ bool operator<(const queueItem& other) const
+ {
+ return at0 < other.at0;
+ }
+ bool operator>=(const queueItem& other) const
+ {
+ return at0 >= other.at0;
+ }
+ bool operator<=(const queueItem& other) const
+ {
+ return at0 <= other.at0;
+ }
+ bool operator!=(const queueItem& other) const
+ {
+ return at0 != other.at0;
+ }
+ bool operator==(const queueItem& other) const
+ {
+ return at0 == other.at0;
+ }
+};
+
+class PriorityQueue
+{
+public:
+ virtual ~PriorityQueue() = 0;
+ virtual unsigned int Size(void) = 0;
+ virtual void Clear(void) = 0;
+ virtual void Insert(unsigned int, unsigned int) = 0;
+ virtual unsigned int Remove(void) = 0;
+ virtual unsigned int LowestPriority(void) = 0;
+ virtual void Kill(std::function pMatch) = 0;
+};
+
+class VanillaPriorityQueue : public PriorityQueue
+{
+public:
+ queueItem queueItems[kPQueueSize + 1];
+ unsigned int fNodeCount; // at2008
+ ~VanillaPriorityQueue();
+ unsigned int Size(void) { return fNodeCount; };
+ void Clear(void);
+ void Upheap(void);
+ void Downheap(unsigned int);
+ void Delete(unsigned int);
+ void Insert(unsigned int, unsigned int);
+ unsigned int Remove(void);
+ unsigned int LowestPriority(void);
+ void Kill(std::function pMatch);
+};
+
+class StdPriorityQueue : public PriorityQueue
+{
+public:
+ std::multiset stdQueue;
+ ~StdPriorityQueue();
+ unsigned int Size(void) { return stdQueue.size(); };
+ void Clear(void);
+ void Insert(unsigned int, unsigned int);
+ unsigned int Remove(void);
+ unsigned int LowestPriority(void);
+ void Kill(std::function pMatch);
+};
diff --git a/source/blood/src/qav.cpp b/source/blood/src/qav.cpp
new file mode 100644
index 000000000..b7a533f34
--- /dev/null
+++ b/source/blood/src/qav.cpp
@@ -0,0 +1,137 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#include "common_game.h"
+#include "qav.h"
+#include "tile.h"
+#include "sfx.h"
+#include "sound.h"
+
+#define kMaxClients 64
+static void (*clientCallback[kMaxClients])(int, void *);
+static int nClients;
+
+
+int qavRegisterClient(void(*pClient)(int, void *))
+{
+ dassert(nClients < kMaxClients);
+ clientCallback[nClients] = pClient;
+
+ return nClients++;
+}
+
+void DrawFrame(int x, int y, TILE_FRAME *pTile, int stat, int shade, int palnum)
+{
+ stat |= pTile->stat;
+ int angle = pTile->angle;
+ if (stat & 0x100)
+ {
+ angle = (angle+1024)&2047;
+ stat &= ~0x100;
+ stat ^= 0x4;
+ }
+ if (stat & kQavOrientationLeft)
+ {
+ stat &= ~kQavOrientationLeft;
+ stat |= 256;
+ }
+ if (palnum <= 0)
+ palnum = pTile->palnum;
+ rotatesprite((x + pTile->x) << 16, (y + pTile->y) << 16, pTile->z, angle,
+ pTile->picnum, ClipRange(pTile->shade + shade, -128, 127), palnum, stat,
+ windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
+}
+
+void QAV::Draw(int ticks, int stat, int shade, int palnum)
+{
+ dassert(ticksPerFrame > 0);
+ int nFrame = ticks / ticksPerFrame;
+ dassert(nFrame >= 0 && nFrame < nFrames);
+ FRAMEINFO *pFrame = &frames[nFrame];
+ for (int i = 0; i < 8; i++)
+ {
+ if (pFrame->tiles[i].picnum > 0)
+ DrawFrame(x, y, &pFrame->tiles[i], stat, shade, palnum);
+ }
+}
+
+void QAV::Play(int start, int end, int nCallback, void *pData)
+{
+ dassert(ticksPerFrame > 0);
+ int frame;
+ int ticks;
+ if (start < 0)
+ frame = (start + 1) / ticksPerFrame;
+ else
+ frame = start / ticksPerFrame + 1;
+
+ for (ticks = ticksPerFrame * frame; ticks <= end; frame++, ticks += ticksPerFrame)
+ {
+ if (frame >= 0 && frame < nFrames)
+ {
+ FRAMEINFO *pFrame = &frames[frame];
+ SOUNDINFO *pSound = &pFrame->sound;
+
+ // by NoOne: handle Sound kill flags
+ if (pSound->sndFlags > 0 && pSound->sndFlags <= kFlagSoundKillAll) {
+ for (int i = 0; i < nFrames; i++) {
+ FRAMEINFO* pFrame2 = &frames[i];
+ SOUNDINFO* pSound2 = &pFrame2->sound;
+ if (pSound2->sound != 0) {
+ if (pSound->sndFlags != kFlagSoundKillAll && pSound2->priority != pSound->priority) continue;
+ else if (nSprite >= 0) {
+ // We need stop all sounds in a range
+ for (int a = 0; a <= pSound2->sndRange; a++)
+ sfxKill3DSound(&sprite[nSprite], -1, pSound2->sound + a);
+ } else {
+ sndKillAllSounds();
+ }
+ }
+ }
+ }
+
+ if (pSound->sound > 0) {
+ int sndRange = pSound->sndRange;
+ if (nSprite == -1) PlaySound(pSound->sound + Random((sndRange == 1) ? 2 : sndRange));
+ else PlaySound3D(&sprite[nSprite], pSound->sound + Random((sndRange == 1) ? 2 : sndRange), 16+pSound->priority, 6);
+ }
+
+ if (pFrame->nCallbackId > 0 && nCallback != -1) {
+ clientCallback[nCallback](pFrame->nCallbackId, pData);
+ }
+ }
+ }
+}
+
+void QAV::Preload(void)
+{
+ for (int i = 0; i < nFrames; i++)
+ {
+ for (int j = 0; j < 8; j++)
+ {
+ if (frames[i].tiles[j].picnum >= 0)
+ tilePreloadTile(frames[i].tiles[j].picnum);
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/blood/src/qav.h b/source/blood/src/qav.h
new file mode 100644
index 000000000..d9afbc6bd
--- /dev/null
+++ b/source/blood/src/qav.h
@@ -0,0 +1,90 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common_game.h"
+#include "blood.h"
+
+#define kQavOrientationLeft 4096
+
+#pragma pack(push, 1)
+
+// by NoOne: add sound flags
+enum
+{
+ kFlagSoundKill = 0x01, // mute QAV sounds of same priority
+ kFlagSoundKillAll = 0x02, // mute all QAV sounds
+
+};
+
+struct TILE_FRAME
+{
+ int picnum;
+ int x;
+ int y;
+ int z;
+ int stat;
+ signed char shade;
+ char palnum;
+ unsigned short angle;
+};
+
+struct SOUNDINFO
+{
+ int sound;
+ unsigned char priority;
+ unsigned char sndFlags; // (by NoOne) Various sound flags
+ unsigned char sndRange; // (by NoOne) Random sound range
+ char reserved[1];
+};
+
+struct FRAMEINFO
+{
+ int nCallbackId; // 0
+ SOUNDINFO sound; // 4
+ TILE_FRAME tiles[8]; // 12
+};
+
+struct QAV
+{
+ char pad1[8]; // 0
+ int nFrames; // 8
+ int ticksPerFrame; // C
+ int at10; // 10
+ int x; // 14
+ int y; // 18
+ int nSprite; // 1c
+ //SPRITE *pSprite; // 1c
+ char pad3[4]; // 20
+ FRAMEINFO frames[1]; // 24
+ void Draw(int ticks, int stat, int shade, int palnum);
+ void Play(int, int, int, void *);
+ void Preload(void);
+
+ void PlaySound(int nSound);
+ void PlaySound3D(spritetype *pSprite, int nSound, int a3, int a4);
+};
+
+#pragma pack(pop)
+
+int qavRegisterClient(void(*pClient)(int, void *));
diff --git a/source/blood/src/qheap.cpp b/source/blood/src/qheap.cpp
new file mode 100644
index 000000000..86955eef5
--- /dev/null
+++ b/source/blood/src/qheap.cpp
@@ -0,0 +1,217 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include
+#include "common_game.h"
+
+#include "qheap.h"
+
+void InstallFenceposts(HEAPNODE *n)
+{
+ char *address = (char*)n;
+ memset(address + 0x20, 0xcc, 0x10);
+ memset(address + n->size - 0x10, 0xcc, 0x10);
+}
+
+void CheckFenceposts(HEAPNODE *n)
+{
+ char *data = (char*)n + 0x20;
+ for (int i = 0; i < 0x10; i++)
+ {
+ if (data[i] != 0xcc)
+ {
+ ThrowError("Block underwritten");
+ }
+ }
+ data = (char*)n + n->size - 0x10;
+ for (int i = 0; i < 0x10; i++)
+ {
+ if (data[i] != 0xcc)
+ {
+ ThrowError("Block overwritten");
+ }
+ }
+}
+
+QHeap::QHeap(int heapSize)
+{
+ dassert(heapSize > 0);
+ size = heapSize;
+ while (size > 0 && (heapPtr = malloc(size)) == NULL)
+ {
+ size -= 0x1000;
+ }
+ if (!heapPtr)
+ {
+ ThrowError("Allocation failure\n");
+ }
+ heap.isFree = false;
+ freeHeap.isFree = false;
+ HEAPNODE *node = (HEAPNODE*)(((intptr_t)heapPtr + 0xf) & ~0xf);
+ heap.next = heap.prev = node;
+ node->next = node->prev = &heap;
+ freeHeap.freeNext = freeHeap.freePrev = node;
+ node->freeNext = node->freePrev = &freeHeap;
+ node->isFree = true;
+ node->size = size & ~0xf;
+}
+
+QHeap::~QHeap(void)
+{
+ Check();
+ free(heapPtr);
+ heapPtr = NULL;
+}
+
+void QHeap::Check(void)
+{
+ HEAPNODE *node = heap.next;
+ while (node != &heap)
+ {
+ if (!node->isFree)
+ {
+ CheckFenceposts(node);
+ }
+ node = node->next;
+ }
+}
+
+void QHeap::Debug(void)
+{
+#if 0
+ char s[4];
+ FILE *f = fopen("MEMFRAG.TXT", "wt");
+ if (!f)
+ {
+ return;
+ }
+ HEAPNODE *node = heap.next;
+ while (node != &heap)
+ {
+ if (node->isFree)
+ {
+ fprintf(f, "%P %10d FREE", node, node->size);
+ }
+ else
+ {
+ char *data = (char*)node + 0x20;
+ for (int i = 0; i < 4; i++)
+ {
+ if (isalpha(data[i]))
+ {
+ s[i] = data[i];
+ }
+ else
+ {
+ s[i] = '_';
+ }
+ }
+ fprintf(f, "%P %10d %4s", node, node->size, s);
+ }
+ }
+ fclose(f);
+#endif
+}
+
+void *QHeap::Alloc(int blockSize)
+{
+ dassert(blockSize > 0);
+ dassert(heapPtr != NULL);
+ if (blockSize > 0)
+ {
+ blockSize = ((blockSize + 0xf) & ~0xf) + 0x40;
+ HEAPNODE *freeNode = freeHeap.freeNext;
+ while (freeNode != &freeHeap)
+ {
+ dassert(freeNode->isFree);
+ if (blockSize <= freeNode->size)
+ {
+ if (blockSize + 0x30 <= freeNode->size)
+ {
+ freeNode->size -= blockSize;
+ HEAPNODE *nextNode = (HEAPNODE *)((char*)freeNode + freeNode->size);
+ nextNode->size = blockSize;
+ nextNode->prev = freeNode;
+ nextNode->next = freeNode->next;
+ nextNode->prev->next = nextNode;
+ nextNode->next->prev = nextNode;
+ nextNode->isFree = false;
+ InstallFenceposts(nextNode);
+ return (void*)((char*)nextNode + 0x30);
+ }
+ else
+ {
+ freeNode->freePrev->freeNext = freeNode->freeNext;
+ freeNode->freeNext->freePrev = freeNode->freePrev;
+ freeNode->isFree = false;
+ InstallFenceposts(freeNode);
+ return (void*)((char*)freeNode + 0x30);
+ }
+ }
+ freeNode = freeNode->freeNext;
+ }
+ }
+ return NULL;
+}
+
+int QHeap::Free(void *p)
+{
+ if (!p)
+ {
+ return 0;
+ }
+ dassert(heapPtr != NULL);
+ HEAPNODE *node = (HEAPNODE*)((char*)p - 0x30);
+ if (node->isFree)
+ {
+ ThrowError("Free on bad or freed block");
+ }
+ CheckFenceposts(node);
+ if (node->prev->isFree)
+ {
+ node->prev->size += node->size;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node = node->prev;
+ }
+ else
+ {
+ node->freeNext = freeHeap.freeNext;
+ node->freePrev = &freeHeap;
+ node->freePrev->freeNext = node;
+ node->freeNext->freePrev = node;
+ node->isFree = true;
+ }
+ HEAPNODE *nextNode = node->next;
+ if (nextNode->isFree)
+ {
+ node->size += nextNode->size;
+ nextNode->freePrev->freeNext = nextNode->freeNext;
+ nextNode->freeNext->freePrev = nextNode->freePrev;
+ nextNode->prev->next = nextNode->next;
+ nextNode->next->prev = nextNode->prev;
+ }
+ return node->size - 0x40;
+}
diff --git a/source/blood/src/qheap.h b/source/blood/src/qheap.h
new file mode 100644
index 000000000..6d036f764
--- /dev/null
+++ b/source/blood/src/qheap.h
@@ -0,0 +1,50 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+struct HEAPNODE
+{
+ HEAPNODE *prev;
+ HEAPNODE *next;
+ int size;
+ bool isFree;
+ HEAPNODE *freePrev;
+ HEAPNODE *freeNext;
+};
+
+class QHeap
+{
+public:
+ QHeap(int heapSize);
+ ~QHeap(void);
+
+ void Check(void);
+ void Debug(void);
+ void *Alloc(int);
+ int Free(void *p);
+
+ void *heapPtr;
+ HEAPNODE heap;
+ HEAPNODE freeHeap;
+ int size;
+};
\ No newline at end of file
diff --git a/source/blood/src/replace.cpp b/source/blood/src/replace.cpp
new file mode 100644
index 000000000..a8c6e9171
--- /dev/null
+++ b/source/blood/src/replace.cpp
@@ -0,0 +1,73 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "common_game.h"
+#include "crc32.h"
+
+#include "globals.h"
+#include "screen.h"
+
+int qanimateoffs(int a1, int a2)
+{
+ int offset = 0;
+ if (a1 >= 0 && a1 < kMaxTiles)
+ {
+ int frames = picanm[a1].num;
+ if (frames > 0)
+ {
+ int vd;
+ if ((a2&0xc000) == 0x8000)
+ vd = (Bcrc32(&a2, 2, 0)+gFrameClock)>>(picanm[a1].sf&PICANM_ANIMSPEED_MASK);
+ else
+ vd = gFrameClock>>(picanm[a1].sf&PICANM_ANIMSPEED_MASK);
+ switch (picanm[a1].sf&PICANM_ANIMTYPE_MASK)
+ {
+ case PICANM_ANIMTYPE_OSC:
+ offset = vd % (2*frames);
+ if (offset >= frames)
+ offset = 2*frames-offset;
+ break;
+ case PICANM_ANIMTYPE_FWD:
+ offset = vd % (frames+1);
+ break;
+ case PICANM_ANIMTYPE_BACK:
+ offset = -(vd % (frames+1));
+ break;
+ }
+ }
+ }
+ return offset;
+}
+
+void qloadpalette()
+{
+ scrLoadPalette();
+}
+
+int32_t qgetpalookup(int32_t a1, int32_t a2)
+{
+ if (gFogMode)
+ return ClipHigh(a1 >> 8, 15) * 16 + ClipRange(a2, 0, 15);
+ else
+ return ClipRange((a1 >> 8) + a2, 0, 63);
+}
\ No newline at end of file
diff --git a/source/blood/src/replace.h b/source/blood/src/replace.h
new file mode 100644
index 000000000..622823eaf
--- /dev/null
+++ b/source/blood/src/replace.h
@@ -0,0 +1,28 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "compat.h"
+
+int qanimateoffs(int a1, int a2);
+void qloadpalette();
+int32_t qgetpalookup(int32_t a1, int32_t a2);
\ No newline at end of file
diff --git a/source/blood/src/resource.cpp b/source/blood/src/resource.cpp
new file mode 100644
index 000000000..d498a7722
--- /dev/null
+++ b/source/blood/src/resource.cpp
@@ -0,0 +1,740 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include
+#include "crc32.h"
+#include "compat.h"
+#include "cache1d.h"
+#ifdef WITHKPLIB
+#include "kplib.h"
+#endif
+#include "common_game.h"
+
+#include "misc.h"
+#include "qheap.h"
+#include "resource.h"
+
+#if B_BIG_ENDIAN == 1
+#include "qav.h"
+#include "seq.h"
+#include "sound.h"
+#endif
+
+CACHENODE Resource::purgeHead = { NULL, &purgeHead, &purgeHead, 0 };
+
+QHeap *Resource::heap;
+
+Resource::Resource(void)
+{
+ dict = NULL;
+ indexName = NULL;
+ indexId = NULL;
+ buffSize = 0;
+ count = 0;
+ handle = -1;
+ crypt = true;
+}
+
+Resource::~Resource(void)
+{
+ if (dict)
+ {
+ for (unsigned int i = 0; i < count; i++)
+ {
+ if (dict[i].type)
+ Free(dict[i].type);
+ if (dict[i].name)
+ Free(dict[i].name);
+ }
+ Free(dict);
+ dict = NULL;
+ buffSize = 0;
+ count = 0;
+ }
+ if (handle != -1)
+ {
+ kclose(handle);
+ }
+}
+
+void Resource::Init(const char *filename)
+{
+ RFFHeader header;
+ dassert(heap != NULL);
+
+ if (filename)
+ {
+ handle = kopen4loadfrommod(filename, 0);
+ if (handle != -1)
+ {
+ int nFileLength = kfilelength(handle);
+ dassert(nFileLength != -1);
+ if (kread(handle, &header, sizeof(RFFHeader)) != sizeof(RFFHeader)
+ || memcmp(header.sign, "RFF\x1a", 4))
+ {
+ ThrowError("RFF header corrupted");
+ }
+#if B_BIG_ENDIAN == 1
+ header.version = B_LITTLE16(header.version);
+ header.offset = B_LITTLE32(header.offset);
+ header.filenum = B_LITTLE32(header.filenum);
+#endif
+ switch (header.version & 0xff00)
+ {
+ case 0x200:
+ crypt = 0;
+ break;
+ case 0x300:
+ crypt = 1;
+ break;
+ default:
+ ThrowError("Unknown RFF version");
+ break;
+ }
+ count = header.filenum;
+ if (count)
+ {
+ buffSize = 1;
+ while (count * 2 >= buffSize)
+ {
+ buffSize *= 2;
+ }
+ dict = (DICTNODE*)Alloc(buffSize * sizeof(DICTNODE));
+ memset(dict, 0, buffSize * sizeof(DICTNODE));
+ DICTNODE_FILE *tdict = (DICTNODE_FILE*)Alloc(count*sizeof(DICTNODE_FILE));
+ int r = klseek(handle, header.offset, SEEK_SET);
+ dassert(r != -1);
+ if ((uint32_t)kread(handle, tdict, count * sizeof(DICTNODE_FILE)) != count*sizeof(DICTNODE_FILE))
+ {
+ ThrowError("RFF dictionary corrupted");
+ }
+ if (crypt)
+ {
+ Crypt(tdict, count * sizeof(DICTNODE_FILE),
+ header.offset + (header.version & 0xff) * header.offset);
+ }
+ for (unsigned int i = 0; i < count; i++)
+ {
+ dict[i].offset = B_LITTLE32(tdict[i].offset);
+ dict[i].size = B_LITTLE32(tdict[i].size);
+ dict[i].flags = tdict[i].flags;
+ int nTypeLength = strnlen(tdict[i].type, 3);
+ int nNameLength = strnlen(tdict[i].name, 8);
+ dict[i].type = (char*)Alloc(nTypeLength+1);
+ dict[i].name = (char*)Alloc(nNameLength+1);
+ strncpy(dict[i].type, tdict[i].type, 3);
+ strncpy(dict[i].name, tdict[i].name, 8);
+ dict[i].type[nTypeLength] = 0;
+ dict[i].name[nNameLength] = 0;
+ dict[i].id = B_LITTLE32(tdict[i].id);
+ }
+ Free(tdict);
+ }
+ }
+ }
+ if (!dict)
+ {
+ buffSize = 16;
+ dict = (DICTNODE*)Alloc(buffSize * sizeof(DICTNODE));
+ memset(dict, 0, buffSize * sizeof(DICTNODE));
+ }
+ Reindex();
+#if 0
+ if (external)
+ {
+ char fname[BMAX_PATH];
+ char type[BMAX_PATH];
+ BDIR *dirr;
+ struct Bdirent *dirent;
+ dirr = Bopendir("./");
+ if (dirr)
+ {
+ while (dirent = Breaddir(dirr))
+ {
+ if (!Bwildmatch(dirent->name, external))
+ continue;
+ _splitpath(dirent->name, NULL, NULL, fname, type);
+ if (type[0] == '.')
+ {
+ AddExternalResource(fname, &type[1], dirent->size);
+ }
+ else
+ {
+ AddExternalResource(fname, "", dirent->size);
+ }
+ }
+ Bclosedir(dirr);
+ }
+#if 0
+ _splitpath2(external, out, &dir, &node, NULL, NULL);
+ _makepath(ext, dir, node, NULL, NULL);
+ int status = _dos_findfirst(external, 0, &info);
+ while (!status)
+ {
+ _splitpath2(info.name, out, NULL, NULL, &fname, &type);
+ if (*type == '.')
+ {
+ AddExternalResource(*fname, (char*)(type + 1), info.size);
+ }
+ else
+ {
+ AddExternalResource(*fname, "", info.size);
+ }
+ status = _dos_findnext(&info);
+ }
+ _dos_findclose(&info);
+#endif
+ }
+#endif
+ for (unsigned int i = 0; i < count; i++)
+ {
+ if (dict[i].flags & DICT_LOCK)
+ {
+ Lock(&dict[i]);
+ }
+ }
+ for (unsigned int i = 0; i < count; i++)
+ {
+ if (dict[i].flags & DICT_LOAD)
+ {
+ Load(&dict[i]);
+ }
+ }
+}
+
+void Resource::Flush(CACHENODE *h)
+{
+ if (h->ptr)
+ {
+ heap->Free(h->ptr);
+ h->ptr = NULL;
+ if (h->lockCount == 0)
+ {
+ RemoveMRU((CACHENODE*)h);
+ return;
+ }
+ h->lockCount = 0;
+ }
+}
+
+void Resource::Purge(void)
+{
+ for (unsigned int i = 0; i < count; i++)
+ {
+ if (dict[i].ptr)
+ {
+ Flush((CACHENODE *)&dict[i]);
+ }
+ }
+}
+
+DICTNODE **Resource::Probe(const char *fname, const char *type)
+{
+ char name[BMAX_PATH];
+ dassert(indexName != NULL);
+ memset(name, 0, sizeof(name));
+ strcpy(name, type);
+ strcat(name, fname);
+ dassert(dict != NULL);
+ unsigned int hash = Bcrc32(name, strlen(name), 0) & (buffSize - 1);
+ unsigned int i = hash;
+ do
+ {
+ if (!indexName[i])
+ {
+ return &indexName[i];
+ }
+ if (!strcmp((*indexName[i]).type, type)
+ && !strcmp((*indexName[i]).name, fname))
+ {
+ return &indexName[i];
+ }
+ if (++i == buffSize)
+ {
+ i = 0;
+ }
+ } while (i != hash);
+ ThrowError("Linear probe failed to find match or unused node!");
+ return NULL;
+}
+
+DICTNODE **Resource::Probe(unsigned int id, const char *type)
+{
+ struct {
+ int id;
+ char type[BMAX_PATH];
+ } name;
+ dassert(indexName != NULL);
+ memset(&name, 0, sizeof(name));
+ strcpy(name.type, type);
+ name.id = id;
+ dassert(dict != NULL);
+ unsigned int hash = Bcrc32(&name, strlen(name.type)+sizeof(name.id), 0) & (buffSize - 1);
+ unsigned int i = hash;
+ do
+ {
+ if (!indexId[i])
+ {
+ return &indexId[i];
+ }
+ if (!strcmp((*indexId[i]).type, type)
+ && (*indexId[i]).id == id)
+ {
+ return &indexId[i];
+ }
+ if (++i == buffSize)
+ {
+ i = 0;
+ }
+ } while (i != hash);
+ ThrowError("Linear probe failed to find match or unused node!");
+ return NULL;
+}
+
+void Resource::Reindex(void)
+{
+ if (indexName)
+ {
+ Free(indexName);
+ }
+ indexName = (DICTNODE **)Alloc(buffSize * sizeof(DICTNODE*));
+ memset(indexName, 0, buffSize * sizeof(DICTNODE*));
+ for (unsigned int i = 0; i < count; i++)
+ {
+ DICTNODE **node = Probe(dict[i].name, dict[i].type);
+ *node = &dict[i];
+ }
+
+ if (indexId)
+ {
+ Free(indexId);
+ }
+ indexId = (DICTNODE **)Alloc(buffSize * sizeof(DICTNODE*));
+ memset(indexId, 0, buffSize * sizeof(DICTNODE*));
+ for (unsigned int i = 0; i < count; i++)
+ {
+ if (dict[i].flags & DICT_ID)
+ {
+ DICTNODE **node = Probe(dict[i].id, dict[i].type);
+ *node = &dict[i];
+ }
+ }
+}
+
+void Resource::Grow(void)
+{
+ buffSize *= 2;
+ void *p = Alloc(buffSize * sizeof(DICTNODE));
+ memset(p, 0, buffSize * sizeof(DICTNODE));
+ memcpy(p, dict, count * sizeof(DICTNODE));
+ Free(dict);
+ dict = (DICTNODE*)p;
+ Reindex();
+}
+
+void Resource::AddExternalResource(const char *name, const char *type, int id)
+{
+ char name2[BMAX_PATH], type2[BMAX_PATH], filename[BMAX_PATH*2];
+ //if (strlen(name) > 8 || strlen(type) > 3) return;
+ sprintf(filename, "%s.%s", name, type);
+ int fhandle = kopen4loadfrommod(filename, 0);
+ if (fhandle == -1)
+ return;
+ int size = kfilelength(fhandle);
+ kclose(fhandle);
+ strcpy(name2, name);
+ strcpy(type2, type);
+ Bstrupr(name2);
+ Bstrupr(type2);
+ dassert(dict != NULL);
+ DICTNODE **index = Probe(name2, type2);
+ dassert(index != NULL);
+ DICTNODE *node = *index;
+ if (node && (node->flags & DICT_EXTERNAL))
+ return;
+ if (!node)
+ {
+ if (2 * count >= buffSize)
+ {
+ Grow();
+ }
+ node = &dict[count++];
+ index = Probe(name2, type2);
+ *index = node;
+ if (node->type)
+ Free(node->type);
+ if (node->name)
+ Free(node->name);
+ int nTypeLength = strlen(type2);
+ int nNameLength = strlen(name2);
+ node->type = (char*)Alloc(nTypeLength+1);
+ node->name = (char*)Alloc(nNameLength+1);
+ strcpy(node->type, type2);
+ strcpy(node->name, name2);
+ }
+ node->size = size;
+ node->flags |= DICT_EXTERNAL;
+ Flush((CACHENODE*)node);
+ if (id != -1)
+ {
+ index = Probe(id, type2);
+ dassert(index != NULL);
+ DICTNODE *node = *index;
+ if (!node)
+ {
+ if (2 * count >= buffSize)
+ {
+ Grow();
+ }
+ node = &dict[count++];
+ index = Probe(id, type2);
+ *index = node;
+ }
+ if (node->type)
+ Free(node->type);
+ if (node->name)
+ Free(node->name);
+ int nTypeLength = strlen(type2);
+ int nNameLength = strlen(name2);
+ node->type = (char*)Alloc(nTypeLength+1);
+ node->name = (char*)Alloc(nNameLength+1);
+ strcpy(node->type, type2);
+ strcpy(node->name, name2);
+ node->id = id;
+ node->size = size;
+ node->flags |= DICT_EXTERNAL;
+ Flush((CACHENODE*)node);
+ }
+}
+
+void *Resource::Alloc(int nSize)
+{
+ dassert(heap != NULL);
+ dassert(nSize != 0);
+ void *p = heap->Alloc(nSize);
+ if (p)
+ {
+ return p;
+ }
+ for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
+ {
+ dassert(node->lockCount == 0);
+ dassert(node->ptr != NULL);
+ int nFree = heap->Free(node->ptr);
+ node->ptr = NULL;
+ RemoveMRU(node);
+ if (nSize <= nFree)
+ {
+ p = Alloc(nSize);
+ dassert(p != NULL);
+ return p;
+ }
+ }
+ ThrowError("Out of memory!");
+ return NULL;
+}
+
+void Resource::Free(void *p)
+{
+ dassert(heap != NULL);
+ dassert(p != NULL);
+ heap->Free(p);
+}
+
+DICTNODE *Resource::Lookup(const char *name, const char *type)
+{
+ char name2[BMAX_PATH], type2[BMAX_PATH];
+ dassert(name != NULL);
+ dassert(type != NULL);
+ //if (strlen(name) > 8 || strlen(type) > 3) return NULL;
+ // Try to load external resource first
+ AddExternalResource(name, type);
+ strcpy(name2, name);
+ strcpy(type2, type);
+ Bstrupr(type2);
+ Bstrupr(name2);
+ return *Probe(name2, type2);
+}
+
+DICTNODE *Resource::Lookup(unsigned int id, const char *type)
+{
+ char type2[BMAX_PATH];
+ dassert(type != NULL);
+ //if (strlen(type) > 3) return NULL;
+ strcpy(type2, type);
+ Bstrupr(type2);
+ return *Probe(id, type2);
+}
+
+void Resource::Read(DICTNODE *n)
+{
+ dassert(n != NULL);
+ Read(n, n->ptr);
+}
+
+void Resource::Read(DICTNODE *n, void *p)
+{
+ char filename[BMAX_PATH];
+ dassert(n != NULL);
+ if (n->flags & DICT_EXTERNAL)
+ {
+ sprintf(filename, "%s.%s", n->name, n->type);
+ int fhandle = kopen4loadfrommod(filename, 0);
+ if (fhandle == -1 || (uint32_t)kread(fhandle, p, n->size) != n->size)
+ {
+ ThrowError("Error reading external resource (%i)", errno);
+ }
+ kclose(fhandle);
+ }
+ else
+ {
+ int r = klseek(handle, n->offset, SEEK_SET);
+ if (r == -1)
+ {
+ ThrowError("Error seeking to resource!");
+ }
+ if ((uint32_t)kread(handle, p, n->size) != n->size)
+ {
+ ThrowError("Error loading resource!");
+ }
+ if (n->flags & DICT_CRYPT)
+ {
+ int size;
+ if (n->size > 0x100)
+ {
+ size = 0x100;
+ }
+ else
+ {
+ size = n->size;
+ }
+ Crypt(n->ptr, size, 0);
+ }
+#if B_BIG_ENDIAN == 1
+ if (!Bstrcmp(n->type, "QAV"))
+ {
+ QAV *qav = (QAV*)p;
+ qav->nFrames = B_LITTLE32(qav->nFrames);
+ qav->ticksPerFrame = B_LITTLE32(qav->ticksPerFrame);
+ qav->at10 = B_LITTLE32(qav->at10);
+ qav->x = B_LITTLE32(qav->x);
+ qav->y = B_LITTLE32(qav->y);
+ qav->nSprite = B_LITTLE32(qav->nSprite);
+ for (int i = 0; i < qav->nFrames; i++)
+ {
+ FRAMEINFO *pFrame = &qav->frames[i];
+ SOUNDINFO *pSound = &pFrame->sound;
+ pFrame->nCallbackId = B_LITTLE32(pFrame->nCallbackId);
+ pSound->sound = B_LITTLE32(pSound->sound);
+ for (int j = 0; j < 8; j++)
+ {
+ TILE_FRAME *pTile = &pFrame->tiles[j];
+ pTile->picnum = B_LITTLE32(pTile->picnum);
+ pTile->x = B_LITTLE32(pTile->x);
+ pTile->y = B_LITTLE32(pTile->y);
+ pTile->z = B_LITTLE32(pTile->z);
+ pTile->stat = B_LITTLE32(pTile->stat);
+ pTile->angle = B_LITTLE16(pTile->angle);
+ }
+ }
+ }
+ else if (!Bstrcmp(n->type, "SEQ"))
+ {
+ Seq *pSeq = (Seq*)p;
+ pSeq->version = B_LITTLE16(pSeq->version);
+ pSeq->nFrames = B_LITTLE16(pSeq->nFrames);
+ pSeq->at8 = B_LITTLE16(pSeq->at8);
+ pSeq->ata = B_LITTLE16(pSeq->ata);
+ pSeq->atc = B_LITTLE32(pSeq->atc);
+ for (int i = 0; i < pSeq->nFrames; i++)
+ {
+ SEQFRAME *pFrame = &pSeq->frames[i];
+ BitReader bitReader((char *)pFrame, sizeof(SEQFRAME));
+ SEQFRAME swapFrame;
+ swapFrame.tile = bitReader.readUnsigned(12);
+ swapFrame.at1_4 = bitReader.readBit();
+ swapFrame.at1_5 = bitReader.readBit();
+ swapFrame.at1_6 = bitReader.readBit();
+ swapFrame.at1_7 = bitReader.readBit();
+ swapFrame.at2_0 = bitReader.readUnsigned(8);
+ swapFrame.at3_0 = bitReader.readUnsigned(8);
+ swapFrame.at4_0 = bitReader.readSigned(8);
+ swapFrame.at5_0 = bitReader.readUnsigned(5);
+ swapFrame.at5_5 = bitReader.readBit();
+ swapFrame.at5_6 = bitReader.readBit();
+ swapFrame.at5_7 = bitReader.readBit();
+ swapFrame.at6_0 = bitReader.readBit();
+ swapFrame.at6_1 = bitReader.readBit();
+ swapFrame.at6_2 = bitReader.readBit();
+ swapFrame.at6_3 = bitReader.readBit();
+ swapFrame.at6_4 = bitReader.readBit();
+ swapFrame.tile2 = bitReader.readUnsigned(4);
+ swapFrame.pad = bitReader.readUnsigned(7);
+ *pFrame = swapFrame;
+ }
+ }
+ else if (!Bstrcmp(n->type, "SFX"))
+ {
+ SFX *pSFX = (SFX*)p;
+ pSFX->relVol = B_LITTLE32(pSFX->relVol);
+ pSFX->pitch = B_LITTLE32(pSFX->pitch);
+ pSFX->pitchRange = B_LITTLE32(pSFX->pitchRange);
+ pSFX->format = B_LITTLE32(pSFX->format);
+ pSFX->loopStart = B_LITTLE32(pSFX->loopStart);
+ }
+#endif
+ }
+}
+
+void *Resource::Load(DICTNODE *h)
+{
+ dassert(h != NULL);
+ if (h->ptr)
+ {
+ if (!h->lockCount)
+ {
+ RemoveMRU((CACHENODE*)h);
+
+ h->prev = purgeHead.prev;
+ purgeHead.prev->next = (CACHENODE*)h;
+ h->next = &purgeHead;
+ purgeHead.prev = (CACHENODE*)h;
+ }
+ }
+ else
+ {
+ h->ptr = Alloc(h->size);
+ Read(h);
+
+ h->prev = purgeHead.prev;
+ purgeHead.prev->next = (CACHENODE*)h;
+ h->next = &purgeHead;
+ purgeHead.prev = (CACHENODE*)h;
+ }
+ return h->ptr;
+}
+
+void *Resource::Load(DICTNODE *h, void *p)
+{
+ dassert(h != NULL);
+ if (p)
+ {
+ Read(h, p);
+ }
+ return p;
+}
+
+void *Resource::Lock(DICTNODE *h)
+{
+ dassert(h != NULL);
+ if (h->ptr)
+ {
+ if (h->lockCount == 0)
+ {
+ RemoveMRU((CACHENODE*)h);
+ }
+ }
+ else
+ {
+ h->ptr = Alloc(h->size);
+ Read(h);
+ }
+
+ h->lockCount++;
+ return h->ptr;
+}
+
+void Resource::Unlock(DICTNODE *h)
+{
+ dassert(h != NULL);
+ dassert(h->ptr != NULL);
+ if (h->lockCount > 0)
+ {
+ h->lockCount--;
+ if (h->lockCount == 0)
+ {
+ h->prev = purgeHead.prev;
+ purgeHead.prev->next = (CACHENODE*)h;
+ h->next = &purgeHead;
+ purgeHead.prev = (CACHENODE*)h;
+ }
+ }
+}
+
+void Resource::Crypt(void *p, int length, unsigned short key)
+{
+ char *cp = (char*)p;
+ for (int i = 0; i < length; i++, key++)
+ {
+ cp[i] ^= (key >> 1);
+ }
+}
+
+void Resource::RemoveMRU(CACHENODE *h)
+{
+ h->prev->next = h->next;
+ h->next->prev = h->prev;
+}
+
+void Resource::FNAddFiles(fnlist_t * fnlist, const char *pattern)
+{
+ char filename[BMAX_PATH];
+ for (unsigned int i = 0; i < count; i++)
+ {
+ DICTNODE *pNode = &dict[i];
+ if (pNode->flags & DICT_EXTERNAL)
+ continue;
+ sprintf(filename, "%s.%s", pNode->name, pNode->type);
+ if (!Bwildmatch(filename, pattern))
+ continue;
+ switch (klistaddentry(&fnlist->findfiles, filename, CACHE1D_FIND_FILE, CACHE1D_SOURCE_GRP))
+ {
+ case -1:
+ return;
+ case 0:
+ fnlist->numfiles++;
+ break;
+ }
+ }
+}
+
+void Resource::PrecacheSounds(void)
+{
+ for (unsigned int i = 0; i < count; i++)
+ {
+ DICTNODE *pNode = &dict[i];
+ if ((!strcmp(pNode->type, "RAW") || !strcmp(pNode->type, "SFX")) && !pNode->ptr)
+ {
+ Load(pNode);
+ G_HandleAsync();
+ }
+ }
+}
+
+void Resource::RemoveNode(DICTNODE* pNode)
+{
+ *pNode = dict[--count];
+ Reindex();
+}
diff --git a/source/blood/src/resource.h b/source/blood/src/resource.h
new file mode 100644
index 000000000..233191be9
--- /dev/null
+++ b/source/blood/src/resource.h
@@ -0,0 +1,127 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "qheap.h"
+
+#pragma pack(push, 1)
+
+enum DICTFLAGS {
+ DICT_ID = 1,
+ DICT_EXTERNAL = 2,
+ DICT_LOAD = 4,
+ DICT_LOCK = 8,
+ DICT_CRYPT = 16,
+};
+
+struct RFFHeader
+{
+ char sign[4];
+ short version;
+ short pad1;
+ unsigned int offset;
+ unsigned int filenum;
+ int pad2[4];
+};
+
+struct DICTNODE_FILE
+{
+ char unused1[16];
+ unsigned int offset;
+ unsigned int size;
+ char unused2[8];
+ char flags;
+ char type[3];
+ char name[8];
+ int id;
+};
+
+#pragma pack(pop)
+
+struct CACHENODE
+{
+ void *ptr;
+ CACHENODE *prev;
+ CACHENODE *next;
+ int lockCount;
+};
+
+struct DICTNODE
+{
+ void *ptr;
+ CACHENODE *prev;
+ CACHENODE *next;
+ int lockCount;
+ unsigned int offset;
+ unsigned int size;
+ char flags;
+ //char type[3];
+ //char name[8];
+ char *type;
+ char *name;
+ unsigned int id;
+};
+
+class Resource
+{
+public:
+ Resource(void);
+ ~Resource(void);
+
+ void Init(const char *filename);
+ static void Flush(CACHENODE *h);
+ void Purge(void);
+ DICTNODE **Probe(const char *fname, const char *type);
+ DICTNODE **Probe(unsigned int id, const char *type);
+ void Reindex(void);
+ void Grow(void);
+ void AddExternalResource(const char *name, const char *type, int id = -1);
+ static void *Alloc(int nSize);
+ static void Free(void *p);
+ DICTNODE *Lookup(const char *name, const char *type);
+ DICTNODE *Lookup(unsigned int id, const char *type);
+ void Read(DICTNODE *n);
+ void Read(DICTNODE *n, void *p);
+ void *Load(DICTNODE *h);
+ void *Load(DICTNODE *h, void *p);
+ void *Lock(DICTNODE *h);
+ void Unlock(DICTNODE *h);
+ void Crypt(void *p, int length, unsigned short key);
+ static void RemoveMRU(CACHENODE *h);
+ int Size(DICTNODE*h) { return h->size; }
+ void FNAddFiles(fnlist_t *fnlist, const char *pattern);
+ void PrecacheSounds(void);
+ void RemoveNode(DICTNODE* pNode);
+
+ DICTNODE *dict;
+ DICTNODE **indexName;
+ DICTNODE **indexId;
+ unsigned int buffSize;
+ unsigned int count;
+ //FILE *handle;
+ int handle;
+ bool crypt;
+
+ static QHeap *heap;
+ static CACHENODE purgeHead;
+};
diff --git a/source/blood/src/screen.cpp b/source/blood/src/screen.cpp
new file mode 100644
index 000000000..f365bfc39
--- /dev/null
+++ b/source/blood/src/screen.cpp
@@ -0,0 +1,286 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include "a.h"
+#include "build.h"
+#include "colmatch.h"
+#include "common_game.h"
+
+#include "blood.h"
+#include "config.h"
+#include "resource.h"
+#include "screen.h"
+
+LOADITEM PLU[15] = {
+ { 0, "NORMAL" },
+ { 1, "SATURATE" },
+ { 2, "BEAST" },
+ { 3, "TOMMY" },
+ { 4, "SPIDER3" },
+ { 5, "GRAY" },
+ { 6, "GRAYISH" },
+ { 7, "SPIDER1" },
+ { 8, "SPIDER2" },
+ { 9, "FLAME" },
+ { 10, "COLD" },
+ { 11, "P1" },
+ { 12, "P2" },
+ { 13, "P3" },
+ { 14, "P4" }
+};
+
+LOADITEM PAL[5] = {
+ { 0, "BLOOD" },
+ { 1, "WATER" },
+ { 2, "BEAST" },
+ { 3, "SEWER" },
+ { 4, "INVULN1" }
+};
+
+
+bool DacInvalid = true;
+static char(*gammaTable)[256];
+RGB curDAC[256];
+RGB baseDAC[256];
+static RGB fromDAC[256];
+static RGB toRGB;
+static RGB *palTable[5];
+static int curPalette;
+static int curGamma;
+int gGammaLevels;
+bool gFogMode = false;
+
+void scrResetPalette(void)
+{
+ paletteSetColorTable(0, (uint8_t*)palTable[0]);
+}
+
+void gSetDacRange(int start, int end, RGB *pPal)
+{
+ UNREFERENCED_PARAMETER(start);
+ UNREFERENCED_PARAMETER(end);
+ if (videoGetRenderMode() == REND_CLASSIC)
+ {
+ memcpy(palette, pPal, sizeof(palette));
+ videoSetPalette(gBrightness>>2, 0, 0);
+ }
+}
+
+void scrLoadPLUs(void)
+{
+ if (gFogMode)
+ {
+ DICTNODE *pFog = gSysRes.Lookup("FOG", "FLU");
+ if (!pFog)
+ ThrowError("FOG.FLU not found");
+ palookup[0] = (char*)gSysRes.Lock(pFog);
+ for (int i = 0; i < 15; i++)
+ palookup[PLU[i].id] = palookup[0];
+ parallaxvisibility = 3072;
+ return;
+ }
+ for (int i = 0; i < 15; i++)
+ {
+ DICTNODE *pPlu = gSysRes.Lookup(PLU[i].name, "PLU");
+ if (!pPlu)
+ ThrowError("%s.PLU not found", PLU[i].name);
+ if (pPlu->size / 256 != 64)
+ ThrowError("Incorrect PLU size");
+ palookup[PLU[i].id] = (char*)gSysRes.Lock(pPlu);
+ }
+#ifdef USE_OPENGL
+ palookupfog[1].r = 255;
+ palookupfog[1].g = 255;
+ palookupfog[1].b = 255;
+#endif
+}
+
+#ifdef USE_OPENGL
+glblend_t const bloodglblend =
+{
+ {
+ { 1.f/3.f, BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 0 },
+ { 2.f/3.f, BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 0 },
+ },
+};
+#endif
+
+void scrLoadPalette(void)
+{
+ paletteloaded = 0;
+ initprintf("Loading palettes\n");
+ for (int i = 0; i < 5; i++)
+ {
+ DICTNODE *pPal = gSysRes.Lookup(PAL[i].name, "PAL");
+ if (!pPal)
+ ThrowError("%s.PAL not found (RFF files may be wrong version)", PAL[i].name);
+ palTable[PAL[i].id] = (RGB*)gSysRes.Lock(pPal);
+ paletteSetColorTable(PAL[i].id, (uint8_t*)palTable[PAL[i].id]);
+ }
+ memcpy(palette, palTable[0], sizeof(palette));
+ numshades = 64;
+ paletteloaded |= PALETTE_MAIN;
+ scrLoadPLUs();
+ paletteloaded |= PALETTE_SHADE;
+ initprintf("Loading translucency table\n");
+ DICTNODE *pTrans = gSysRes.Lookup("TRANS", "TLU");
+ if (!pTrans)
+ ThrowError("TRANS.TLU not found");
+ blendtable[0] = (char*)gSysRes.Lock(pTrans);
+ paletteloaded |= PALETTE_TRANSLUC;
+
+#ifdef USE_OPENGL
+ for (auto & x : glblend)
+ x = bloodglblend;
+#endif
+
+ initfastcolorlookup_palette(palette);
+ palettePostLoadTables();
+}
+
+void scrSetPalette(int palId)
+{
+ curPalette = palId;
+ scrSetGamma(0/*curGamma*/);
+}
+
+void scrSetGamma(int nGamma)
+{
+ dassert(nGamma < gGammaLevels);
+ curGamma = nGamma;
+ for (int i = 0; i < 256; i++)
+ {
+ baseDAC[i].red = gammaTable[curGamma][palTable[curPalette][i].red];
+ baseDAC[i].green = gammaTable[curGamma][palTable[curPalette][i].green];
+ baseDAC[i].blue = gammaTable[curGamma][palTable[curPalette][i].blue];
+ }
+ DacInvalid = 1;
+}
+
+void scrSetupFade(char red, char green, char blue)
+{
+ memcpy(fromDAC, curDAC, sizeof(fromDAC));
+ toRGB.red = red;
+ toRGB.green = green;
+ toRGB.blue = blue;
+}
+
+void scrSetupUnfade(void)
+{
+ memcpy(fromDAC, baseDAC, sizeof(fromDAC));
+}
+
+void scrFadeAmount(int amount)
+{
+ for (int i = 0; i < 256; i++)
+ {
+ curDAC[i].red = interpolate(fromDAC[i].red, toRGB.red, amount);
+ curDAC[i].green = interpolate(fromDAC[i].green, toRGB.green, amount);
+ curDAC[i].blue = interpolate(fromDAC[i].blue, toRGB.blue, amount);
+ }
+ gSetDacRange(0, 256, curDAC);
+}
+
+void scrSetDac(void)
+{
+ if (DacInvalid)
+ gSetDacRange(0, 256, baseDAC);
+ DacInvalid = 0;
+}
+
+void scrInit(void)
+{
+ initprintf("Initializing engine\n");
+#ifdef USE_OPENGL
+ glrendmode = REND_POLYMOST;
+#endif
+ engineInit();
+ curPalette = 0;
+ curGamma = 0;
+ initprintf("Loading gamma correction table\n");
+ DICTNODE *pGamma = gSysRes.Lookup("gamma", "DAT");
+ if (!pGamma)
+ ThrowError("Gamma table not found");
+ gGammaLevels = pGamma->size / 256;
+ gammaTable = (char(*)[256])gSysRes.Lock(pGamma);
+}
+
+void scrUnInit(void)
+{
+ memset(palookup, 0, sizeof(palookup));
+ memset(blendtable, 0, sizeof(blendtable));
+ engineUnInit();
+}
+
+
+void scrSetGameMode(int vidMode, int XRes, int YRes, int nBits)
+{
+ videoResetMode();
+ //videoSetGameMode(vidMode, XRes, YRes, nBits, 0);
+ if (videoSetGameMode(vidMode, XRes, YRes, nBits, 0) < 0)
+ {
+ initprintf("Failure setting video mode %dx%dx%d %s! Trying next mode...\n", XRes, YRes,
+ nBits, vidMode ? "fullscreen" : "windowed");
+
+ int resIdx = 0;
+
+ for (int i=0; i < validmodecnt; i++)
+ {
+ if (validmode[i].xdim == XRes && validmode[i].ydim == YRes)
+ {
+ resIdx = i;
+ break;
+ }
+ }
+
+ int const savedIdx = resIdx;
+ int bpp = nBits;
+
+ while (videoSetGameMode(0, validmode[resIdx].xdim, validmode[resIdx].ydim, bpp, 0) < 0)
+ {
+ initprintf("Failure setting video mode %dx%dx%d windowed! Trying next mode...\n",
+ validmode[resIdx].xdim, validmode[resIdx].ydim, bpp);
+
+ if (++resIdx == validmodecnt)
+ {
+ if (bpp == 8)
+ ThrowError("Fatal error: unable to set any video mode!");
+
+ resIdx = savedIdx;
+ bpp = 8;
+ }
+ }
+
+ gSetup.xdim = validmode[resIdx].xdim;
+ gSetup.ydim = validmode[resIdx].ydim;
+ gSetup.bpp = bpp;
+ }
+ videoClearViewableArea(0);
+ scrNextPage();
+ scrSetPalette(curPalette);
+}
+
+void scrNextPage(void)
+{
+ videoNextPage();
+}
diff --git a/source/blood/src/screen.h b/source/blood/src/screen.h
new file mode 100644
index 000000000..84ab93ff3
--- /dev/null
+++ b/source/blood/src/screen.h
@@ -0,0 +1,55 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+
+struct LOADITEM {
+ int id;
+ const char *name;
+};
+
+#pragma pack(push, 1)
+struct RGB {
+ char red, green, blue;
+};
+#pragma pack(pop)
+
+extern bool DacInvalid;
+extern RGB curDAC[256];
+extern RGB baseDAC[256];
+extern int gGammaLevels;
+extern bool gFogMode;
+void scrResetPalette(void);
+void gSetDacRange(int start, int end, RGB *pPal);
+void scrLoadPLUs(void);
+void scrLoadPalette(void);
+void scrSetPalette(int palId);
+void scrSetGamma(int nGamma);
+void scrSetupFade(char red, char green, char blue);
+void scrSetupUnfade(void);
+void scrFadeAmount(int amount);
+void scrSetDac(void);
+void scrInit(void);
+void scrUnInit(void);
+void scrSetGameMode(int vidMode, int XRes, int YRes, int nBits);
+void scrNextPage(void);
\ No newline at end of file
diff --git a/source/blood/src/sdlmusic.cpp b/source/blood/src/sdlmusic.cpp
new file mode 100644
index 000000000..48a42c983
--- /dev/null
+++ b/source/blood/src/sdlmusic.cpp
@@ -0,0 +1,544 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010 EDuke32 developers and contributors
+
+This file is part of EDuke32.
+
+EDuke32 is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+/*
+ * A reimplementation of Jim Dose's FX_MAN routines, using SDL_mixer 1.2.
+ * Whee. FX_MAN is also known as the "Apogee Sound System", or "ASS" for
+ * short. How strangely appropriate that seems.
+ */
+
+// This object is shared by all Build games with MIDI playback!
+
+#define NEED_SDL_MIXER
+
+#include "compat.h"
+
+#include "common_game.h"
+#include "cache1d.h"
+
+#include "sdlayer.h"
+#include "music.h"
+#include "al_midi.h"
+#include "oplmidi.h"
+
+#if !defined _WIN32 && !defined(GEKKO)
+//# define FORK_EXEC_MIDI 1
+#endif
+
+#if defined FORK_EXEC_MIDI // fork/exec based external midi player
+#include
+#include
+#include
+static char **external_midi_argv;
+static pid_t external_midi_pid=-1;
+static int8_t external_midi_restart=0;
+#endif
+
+#ifdef __ANDROID__ //TODO fix
+static char const *external_midi_tempfn = APPBASENAME "-music.mid";
+#else
+static char const *external_midi_tempfn = "/tmp/" APPBASENAME "-music.mid";
+#endif
+
+static int32_t external_midi = 0;
+
+int32_t MUSIC_SoundDevice = MIDIDEVICE_NONE;
+int32_t MUSIC_ErrorCode = MUSIC_Ok;
+
+static OPLMusic::midifuncs MUSIC_MidiFunctions;
+#define MUSIC_SetErrorCode(status) MUSIC_ErrorCode = (status);
+
+static char warningMessage[80];
+static char errorMessage[80];
+
+static int32_t music_initialized = 0;
+static int32_t music_context = 0;
+static int32_t music_loopflag = MUSIC_PlayOnce;
+static Mix_Music *music_musicchunk = NULL;
+
+static void setErrorMessage(const char *msg)
+{
+ Bstrncpyz(errorMessage, msg, sizeof(errorMessage));
+}
+
+// The music functions...
+
+const char *MUSIC_ErrorString(int32_t ErrorNumber)
+{
+ switch (ErrorNumber)
+ {
+ case MUSIC_Warning:
+ return warningMessage;
+
+ case MUSIC_Error:
+ return errorMessage;
+
+ case MUSIC_Ok:
+ return "OK; no error.";
+
+ case MUSIC_MidiError:
+ return "MIDI error.";
+
+ default:
+ return "Unknown error.";
+ } // switch
+
+ return NULL;
+} // MUSIC_ErrorString
+
+int32_t MUSIC_Init(int32_t SoundCard, int32_t Address)
+{
+ MUSIC_SoundDevice = SoundCard;
+ if (SoundCard == MIDIDEVICE_OPL)
+ {
+ return OPLMusic::MUSIC_InitMidi(SoundCard, &MUSIC_MidiFunctions, Address);
+ }
+#ifdef __ANDROID__
+ music_initialized = 1;
+ return MUSIC_Ok;
+#endif
+ // Use an external MIDI player if the user has specified to do so
+ char *command = getenv("EDUKE32_MUSIC_CMD");
+ const SDL_version *linked = Mix_Linked_Version();
+
+ UNREFERENCED_PARAMETER(SoundCard);
+ UNREFERENCED_PARAMETER(Address);
+
+ if (music_initialized)
+ {
+ setErrorMessage("Music system is already initialized.");
+ return MUSIC_Error;
+ } // if
+
+ if (SDL_VERSIONNUM(linked->major,linked->minor,linked->patch) < MIX_REQUIREDVERSION)
+ {
+ // reject running with SDL_Mixer versions older than what is stated in sdl_inc.h
+ initprintf("You need at least v%d.%d.%d of SDL_mixer for music\n",SDL_MIXER_MIN_X,SDL_MIXER_MIN_Y,SDL_MIXER_MIN_Z);
+ return MUSIC_Error;
+ }
+
+ external_midi = (command != NULL && command[0] != 0);
+
+ if (external_midi)
+ {
+#if defined FORK_EXEC_MIDI
+ int32_t ws=1, numargs=0, pagesize=sysconf(_SC_PAGE_SIZE);
+ char *c, *cmd;
+ size_t sz;
+#endif
+
+ initprintf("Setting music command to \"%s\".\n", command);
+
+#if !defined FORK_EXEC_MIDI
+ if (Mix_SetMusicCMD(command)==-1)
+ {
+ perror("Mix_SetMusicCMD");
+ goto fallback;
+ }
+#else
+
+ if (pagesize==-1)
+ goto fallback;
+
+ for (c=command; *c; c++)
+ {
+ if (isspace(*c))
+ ws = 1;
+ else if (ws)
+ {
+ ws = 0;
+ numargs++;
+ }
+ }
+
+ if (numargs==0)
+ goto fallback;
+
+ sz = (numargs+2)*sizeof(char *) + (c-command+1);
+ sz = ((sz+pagesize-1)/pagesize)*pagesize;
+#if defined(__APPLE__) || defined(__ANDROID__)
+ external_midi_argv = Xcalloc(1,sz+pagesize);
+ external_midi_argv = (char **)((intptr_t)external_midi_argv + (pagesize-(((intptr_t)external_midi_argv)&(pagesize-1))));
+#else
+ if (posix_memalign((void **)&external_midi_argv, pagesize, sz))
+ goto fallback;
+#endif
+ cmd = (char *)external_midi_argv + (numargs+2)*sizeof(char *);
+ Bmemcpy(cmd, command, c-command+1);
+
+ ws = 1;
+ numargs = 0;
+ for (c=cmd; *c; c++)
+ {
+ if (isspace(*c))
+ {
+ ws = 1;
+ *c = 0;
+ }
+ else if (ws)
+ {
+ ws = 0;
+ external_midi_argv[numargs++] = c;
+ }
+ }
+ external_midi_argv[numargs] = external_midi_tempfn;
+ external_midi_argv[numargs+1] = NULL;
+
+ if (mprotect(external_midi_argv, sz, PROT_READ)==-1) // make argv and command string read-only
+ {
+ perror("MUSIC_Init: mprotect");
+ goto fallback;
+ }
+# if 0
+ {
+ int i;
+ initprintf("----Music argv:\n");
+ for (i=0; i=0; i--)
+ {
+ fp = Bfopen(s[i], "r");
+ if (fp == NULL)
+ {
+ if (i == 0)
+ {
+ initprintf("Error: couldn't open any of the following files:\n");
+ for (i = ARRAY_SIZE(s)-1; i>=0; i--)
+ initprintf("%s\n",s[i]);
+ return MUSIC_Error;
+ }
+ continue;
+ }
+ else break;
+ }
+ Bfclose(fp);
+ }
+
+ music_initialized = 1;
+ return MUSIC_Ok;
+} // MUSIC_Init
+
+
+int32_t MUSIC_Shutdown(void)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::MIDI_StopSong();
+
+ return MUSIC_Ok;
+ }
+ // TODO - make sure this is being called from the menu -- SA
+#if !defined FORK_EXEC_MIDI
+ if (external_midi)
+ Mix_SetMusicCMD(NULL);
+#endif
+
+ MUSIC_StopSong();
+ music_context = 0;
+ music_initialized = 0;
+ music_loopflag = MUSIC_PlayOnce;
+
+ return MUSIC_Ok;
+} // MUSIC_Shutdown
+
+
+void MUSIC_SetMaxFMMidiChannel(int32_t channel)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::AL_SetMaxMidiChannel(channel);
+ }
+ // UNREFERENCED_PARAMETER(channel);
+} // MUSIC_SetMaxFMMidiChannel
+
+
+void MUSIC_SetVolume(int32_t volume)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::MIDI_SetVolume(min(max(0, volume), 255));
+ return;
+ }
+ volume = max(0, volume);
+ volume = min(volume, 255);
+
+ Mix_VolumeMusic(volume >> 1); // convert 0-255 to 0-128.
+} // MUSIC_SetVolume
+
+
+int32_t MUSIC_GetVolume(void)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ return OPLMusic::MIDI_GetVolume();
+ }
+ return (Mix_VolumeMusic(-1) << 1); // convert 0-128 to 0-255.
+} // MUSIC_GetVolume
+
+
+void MUSIC_SetLoopFlag(int32_t loopflag)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::MIDI_SetLoopFlag(loopflag);
+ return;
+ }
+ music_loopflag = loopflag;
+} // MUSIC_SetLoopFlag
+
+
+void MUSIC_Continue(void)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::MIDI_ContinueSong();
+ return;
+ }
+ if (Mix_PausedMusic())
+ Mix_ResumeMusic();
+} // MUSIC_Continue
+
+
+void MUSIC_Pause(void)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::MIDI_PauseSong();
+ return;
+ }
+ Mix_PauseMusic();
+} // MUSIC_Pause
+
+int32_t MUSIC_StopSong(void)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ OPLMusic::MIDI_StopSong();
+ MUSIC_SetErrorCode(MUSIC_Ok);
+ return MUSIC_Ok;
+ }
+#if defined FORK_EXEC_MIDI
+ if (external_midi)
+ {
+ if (external_midi_pid > 0)
+ {
+ int32_t ret;
+ struct timespec ts;
+
+ external_midi_restart = 0; // make SIGCHLD handler a no-op
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 5000000; // sleep 5ms at most
+
+ kill(external_midi_pid, SIGTERM);
+ nanosleep(&ts, NULL);
+ ret = waitpid(external_midi_pid, NULL, WNOHANG|WUNTRACED);
+// printf("(%d)", ret);
+
+ if (ret != external_midi_pid)
+ {
+ if (ret==-1)
+ perror("waitpid");
+ else
+ {
+ // we tried to be nice, but no...
+ kill(external_midi_pid, SIGKILL);
+ initprintf("%s: wait for SIGTERM timed out.\n", __func__);
+ if (waitpid(external_midi_pid, NULL, WUNTRACED)==-1)
+ perror("waitpid (2)");
+ }
+ }
+
+ external_midi_pid = -1;
+ }
+
+ return MUSIC_Ok;
+ }
+#endif
+
+ //if (!fx_initialized)
+ if (!Mix_QuerySpec(NULL, NULL, NULL))
+ {
+ setErrorMessage("Need FX system initialized, too. Sorry.");
+ return MUSIC_Error;
+ } // if
+
+ if ((Mix_PlayingMusic()) || (Mix_PausedMusic()))
+ Mix_HaltMusic();
+
+ if (music_musicchunk)
+ Mix_FreeMusic(music_musicchunk);
+
+ music_musicchunk = NULL;
+
+ return MUSIC_Ok;
+} // MUSIC_StopSong
+
+#if defined FORK_EXEC_MIDI
+static int32_t playmusic()
+{
+ pid_t pid = vfork();
+
+ if (pid==-1) // error
+ {
+ initprintf("%s: vfork: %s\n", __func__, strerror(errno));
+ return MUSIC_Error;
+ }
+ else if (pid==0) // child
+ {
+ // exec without PATH lookup
+ if (execv(external_midi_argv[0], external_midi_argv) < 0)
+ {
+ perror("execv");
+ _exit(1);
+ }
+ }
+ else // parent
+ {
+ external_midi_pid = pid;
+ }
+
+ return MUSIC_Ok;
+}
+
+static void sigchld_handler(int signo)
+{
+ if (signo==SIGCHLD && external_midi_restart)
+ {
+ int status;
+
+ if (external_midi_pid > 0)
+ {
+ if (waitpid(external_midi_pid, &status, WUNTRACED)==-1)
+ perror("waitpid (3)");
+
+ if (WIFEXITED(status) && WEXITSTATUS(status)==0)
+ {
+ // loop ...
+ playmusic();
+ }
+ }
+ }
+}
+#endif
+
+// Duke3D-specific. --ryan.
+// void MUSIC_PlayMusic(char *_filename)
+int32_t MUSIC_PlaySong(char *song, int32_t songsize, int32_t loopflag)
+{
+ if (MUSIC_SoundDevice == MIDIDEVICE_OPL)
+ {
+ MUSIC_SetErrorCode(MUSIC_Ok)
+
+ if (OPLMusic::MIDI_PlaySong(song, loopflag) != OPLMusic::MIDI_Ok)
+ {
+ MUSIC_SetErrorCode(MUSIC_MidiError);
+ return MUSIC_Warning;
+ }
+
+ return MUSIC_Ok;
+ }
+ if (external_midi)
+ {
+ FILE *fp;
+
+#if defined FORK_EXEC_MIDI
+ static int32_t sigchld_handler_set = 0;
+
+ if (!sigchld_handler_set)
+ {
+ struct sigaction sa;
+ sa.sa_handler=sigchld_handler;
+ sa.sa_flags=0;
+ sigemptyset(&sa.sa_mask);
+
+ if (sigaction(SIGCHLD, &sa, NULL)==-1)
+ initprintf("%s: sigaction: %s\n", __func__, strerror(errno));
+
+ sigchld_handler_set = 1;
+ }
+#endif
+
+ fp = Bfopen(external_midi_tempfn, "wb");
+ if (fp)
+ {
+ fwrite(song, 1, songsize, fp);
+ Bfclose(fp);
+
+#if defined FORK_EXEC_MIDI
+ external_midi_restart = loopflag;
+ int32_t retval = playmusic();
+ if (retval != MUSIC_Ok)
+ return retval;
+#else
+ music_musicchunk = Mix_LoadMUS(external_midi_tempfn);
+ if (!music_musicchunk)
+ {
+ initprintf("Mix_LoadMUS: %s\n", Mix_GetError());
+ return MUSIC_Error;
+ }
+#endif
+ }
+ else
+ {
+ initprintf("%s: fopen: %s\n", __func__, strerror(errno));
+ return MUSIC_Error;
+ }
+ }
+ else
+ music_musicchunk = Mix_LoadMUS_RW(SDL_RWFromMem(song, songsize)
+#if (SDL_MAJOR_VERSION > 1)
+ , SDL_FALSE
+#endif
+ );
+
+ if (music_musicchunk == NULL)
+ return MUSIC_Error;
+
+ if (Mix_PlayMusic(music_musicchunk, (loopflag == MUSIC_LoopSong)?-1:0) == -1)
+ {
+ initprintf("Mix_PlayMusic: %s\n", Mix_GetError());
+ return MUSIC_Error;
+ }
+
+ return MUSIC_Ok;
+}
+
+
+void MUSIC_Update(void)
+{}
diff --git a/source/blood/src/sectorfx.cpp b/source/blood/src/sectorfx.cpp
new file mode 100644
index 000000000..9ac60299a
--- /dev/null
+++ b/source/blood/src/sectorfx.cpp
@@ -0,0 +1,424 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "compat.h"
+#include "build.h"
+#include "pragmas.h"
+#include "common_game.h"
+
+#include "blood.h"
+#include "db.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "trig.h"
+
+char flicker1[] = {
+ 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0,
+ 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1,
+ 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1
+};
+
+char flicker2[] = {
+ 1, 2, 4, 2, 3, 4, 3, 2, 0, 0, 1, 2, 4, 3, 2, 0,
+ 2, 1, 0, 1, 0, 2, 3, 4, 3, 2, 1, 1, 2, 0, 0, 1,
+ 1, 2, 3, 4, 4, 3, 2, 1, 2, 3, 4, 4, 2, 1, 0, 1,
+ 0, 0, 0, 0, 1, 2, 3, 4, 3, 2, 1, 2, 3, 4, 3, 2
+};
+
+char flicker3[] = {
+ 4, 4, 4, 4, 3, 4, 4, 4, 4, 4, 4, 2, 4, 3, 4, 4,
+ 4, 4, 2, 1, 3, 3, 3, 4, 3, 4, 4, 4, 4, 4, 2, 4,
+ 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 1, 0, 1,
+ 0, 1, 0, 1, 0, 2, 3, 4, 4, 4, 4, 4, 4, 4, 3, 4
+};
+
+char flicker4[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 3, 0, 1, 0, 1, 0, 4, 4, 4, 4, 4, 2, 0,
+ 0, 0, 0, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 2, 1, 2, 1, 2, 1, 2, 1, 4, 3, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0 ,0 ,0, 0, 0 ,0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0 ,0 ,0, 0, 0 ,0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0 ,0 ,0, 0, 0 ,0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0 ,0 ,0, 0, 0 ,0, 0
+};
+
+char strobe[] = {
+ 64, 64, 64, 48, 36, 27, 20, 15, 11, 9, 6, 5, 4, 3, 2, 2,
+ 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+int GetWaveValue(int a, int b, int c)
+{
+ b &= 2047;
+ switch (a)
+ {
+ case 0:
+ return c;
+ case 1:
+ return (b>>10)*c;
+ case 2:
+ return (klabs(128-(b>>3))*c)>>7;
+ case 3:
+ return ((b>>3)*c)>>8;
+ case 4:
+ return ((255-(b>>3))*c)>>8;
+ case 5:
+ return (c+mulscale30(c,Sin(b)))>>1;
+ case 6:
+ return flicker1[b>>5]*c;
+ case 7:
+ return (flicker2[b>>5]*c)>>2;
+ case 8:
+ return (flicker3[b>>5]*c)>>2;
+ case 9:
+ return (flicker4[b>>4]*c)>>2;
+ case 10:
+ return (strobe[b>>5]*c)>>6;
+ case 11:
+ if (b*4 > 2048)
+ return 0;
+ return (c-mulscale30(c, Cos(b*4)))>>1;
+ }
+ return 0;
+}
+
+int shadeCount;
+short shadeList[512];
+
+void DoSectorLighting(void)
+{
+ for (int i = 0; i < shadeCount; i++)
+ {
+ int nXSector = shadeList[i];
+ XSECTOR *pXSector = &xsector[nXSector];
+ int nSector = pXSector->reference;
+ dassert(sector[nSector].extra == nXSector);
+ if (pXSector->shade)
+ {
+ int v4 = pXSector->shade;
+ if (pXSector->shadeFloor)
+ {
+ sector[nSector].floorshade -= v4;
+ if (pXSector->color)
+ {
+ int nTemp = pXSector->floorpal;
+ pXSector->floorpal = sector[nSector].floorpal;
+ sector[nSector].floorpal = nTemp;
+ }
+ }
+ if (pXSector->shadeCeiling)
+ {
+ sector[nSector].ceilingshade -= v4;
+ if (pXSector->color)
+ {
+ int nTemp = pXSector->ceilpal;
+ pXSector->ceilpal = sector[nSector].ceilingpal;
+ sector[nSector].ceilingpal = nTemp;
+ }
+ }
+ if (pXSector->shadeWalls)
+ {
+ int nStartWall = sector[nSector].wallptr;
+ int nEndWall = nStartWall + sector[nSector].wallnum;
+ for (int j = nStartWall; j < nEndWall; j++)
+ {
+ wall[j].shade -= v4;
+ if (pXSector->color)
+ {
+ wall[j].pal = sector[nSector].floorpal;
+ }
+ }
+ }
+ pXSector->shade = 0;
+ }
+ if (pXSector->shadeAlways || pXSector->busy)
+ {
+ int t1 = pXSector->wave;
+ int t2 = pXSector->amplitude;
+ if (!pXSector->shadeAlways && pXSector->busy)
+ {
+ t2 = mulscale16(t2, pXSector->busy);
+ }
+ int v4 = GetWaveValue(t1, pXSector->phase*8+pXSector->freq*gGameClock, t2);
+ if (pXSector->shadeFloor)
+ {
+ sector[nSector].floorshade = ClipRange(sector[nSector].floorshade+v4, -128, 127);
+ if (pXSector->color && v4 != 0)
+ {
+ int nTemp = pXSector->floorpal;
+ pXSector->floorpal = sector[nSector].floorpal;
+ sector[nSector].floorpal = nTemp;
+ }
+ }
+ if (pXSector->shadeCeiling)
+ {
+ sector[nSector].ceilingshade = ClipRange(sector[nSector].ceilingshade+v4, -128, 127);
+ if (pXSector->color && v4 != 0)
+ {
+ int nTemp = pXSector->ceilpal;
+ pXSector->ceilpal = sector[nSector].ceilingpal;
+ sector[nSector].ceilingpal = nTemp;
+ }
+ }
+ if (pXSector->shadeWalls)
+ {
+ int nStartWall = sector[nSector].wallptr;
+ int nEndWall = nStartWall + sector[nSector].wallnum;
+ for (int j = nStartWall; j < nEndWall; j++)
+ {
+ wall[j].shade = ClipRange(wall[j].shade+v4, -128, 127);
+ if (pXSector->color && v4 != 0)
+ {
+ wall[j].pal = sector[nSector].floorpal;
+ }
+ }
+ }
+ pXSector->shade = v4;
+ }
+ }
+}
+
+void UndoSectorLighting(void)
+{
+ for (int i = 0; i < numsectors; i++)
+ {
+ int nXSprite = sector[i].extra;
+ if (nXSprite > 0)
+ {
+ XSECTOR *pXSector = &xsector[i];
+ if (pXSector->shade)
+ {
+ int v4 = pXSector->shade;
+ if (pXSector->shadeFloor)
+ {
+ sector[i].floorshade -= v4;
+ if (pXSector->color)
+ {
+ int nTemp = pXSector->floorpal;
+ pXSector->floorpal = sector[i].floorpal;
+ sector[i].floorpal = nTemp;
+ }
+ }
+ if (pXSector->shadeCeiling)
+ {
+ sector[i].ceilingshade -= v4;
+ if (pXSector->color)
+ {
+ int nTemp = pXSector->ceilpal;
+ pXSector->ceilpal = sector[i].ceilingpal;
+ sector[i].ceilingpal = nTemp;
+ }
+ }
+ if (pXSector->shadeWalls)
+ {
+ int nStartWall = sector[i].wallptr;
+ int nEndWall = nStartWall + sector[i].wallnum;
+ for (int j = nStartWall; j < nEndWall; j++)
+ {
+ wall[j].shade -= v4;
+ if (pXSector->color)
+ {
+ wall[j].pal = sector[i].floorpal;
+ }
+ }
+ }
+ pXSector->shade = 0;
+ }
+ }
+ }
+}
+
+int panCount;
+short panList[kMaxXSectors];
+short wallPanList[kMaxXWalls];
+int wallPanCount;
+
+void DoSectorPanning(void)
+{
+ for (int i = 0; i < panCount; i++)
+ {
+ int nXSector = panList[i];
+ XSECTOR *pXSector = &xsector[nXSector];
+ int nSector = pXSector->reference;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype *pSector = §or[nSector];
+ dassert(pSector->extra == nXSector);
+ if (pXSector->panAlways || pXSector->busy)
+ {
+ int angle = pXSector->panAngle+1024;
+ int speed = pXSector->panVel<<10;
+ if (!pXSector->panAlways && (pXSector->busy&0xffff))
+ speed = mulscale16(speed, pXSector->busy);
+
+ if (pXSector->panFloor) // Floor
+ {
+ int nTile = pSector->floorpicnum;
+ int px = (pSector->floorxpanning<<8)+pXSector->at32_1;
+ int py = (pSector->floorypanning<<8)+pXSector->at34_0;
+ if (pSector->floorstat&64)
+ angle -= 512;
+ int xBits = (picsiz[nTile]&15)-((pSector->floorstat&8)!=0);
+ px += mulscale30(speed<<2, Cos(angle))>>xBits;
+ int yBits = (picsiz[nTile]/16)-((pSector->floorstat&8)!=0);
+ py -= mulscale30(speed<<2, Sin(angle))>>yBits;
+ pSector->floorxpanning = px>>8;
+ pSector->floorypanning = py>>8;
+ pXSector->at32_1 = px&255;
+ pXSector->at34_0 = py&255;
+ }
+ if (pXSector->panCeiling) // Ceiling
+ {
+ int nTile = pSector->ceilingpicnum;
+ int px = (pSector->ceilingxpanning<<8)+pXSector->at30_1;
+ int py = (pSector->ceilingypanning<<8)+pXSector->at31_1;
+ if (pSector->ceilingstat&64)
+ angle -= 512;
+ int xBits = (picsiz[nTile]&15)-((pSector->ceilingstat&8)!=0);
+ px += mulscale30(speed<<2, Cos(angle))>>xBits;
+ int yBits = (picsiz[nTile]/16)-((pSector->ceilingstat&8)!=0);
+ py -= mulscale30(speed<<2, Sin(angle))>>yBits;
+ pSector->ceilingxpanning = px>>8;
+ pSector->ceilingypanning = py>>8;
+ pXSector->at30_1 = px&255;
+ pXSector->at31_1 = py&255;
+ }
+ }
+ }
+ for (int i = 0; i < wallPanCount; i++)
+ {
+ int nXWall = wallPanList[i];
+ XWALL *pXWall = &xwall[nXWall];
+ int nWall = pXWall->reference;
+ dassert(wall[nWall].extra == nXWall);
+ if (pXWall->panAlways || pXWall->busy)
+ {
+ int psx = pXWall->panXVel<<10;
+ int psy = pXWall->panYVel<<10;
+ if (!pXWall->panAlways && (pXWall->busy & 0xffff))
+ {
+ psx = mulscale16(psx, pXWall->busy);
+ psy = mulscale16(psy, pXWall->busy);
+ }
+ int nTile = wall[nWall].picnum;
+ int px = (wall[nWall].xpanning<<8)+pXWall->xpanFrac;
+ int py = (wall[nWall].ypanning<<8)+pXWall->ypanFrac;
+ px += (psx<<2)>>((uint8_t)picsiz[nTile]&15);
+ py += (psy<<2)>>((uint8_t)picsiz[nTile]/16);
+ wall[nWall].xpanning = px>>8;
+ wall[nWall].ypanning = py>>8;
+ pXWall->xpanFrac = px&255;
+ pXWall->ypanFrac = py&255;
+ }
+ }
+}
+
+void InitSectorFX(void)
+{
+ shadeCount = 0;
+ panCount = 0;
+ wallPanCount = 0;
+ for (int i = 0; i < numsectors; i++)
+ {
+ int nXSector = sector[i].extra;
+ if (nXSector > 0)
+ {
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (pXSector->amplitude)
+ shadeList[shadeCount++] = nXSector;
+ if (pXSector->panVel)
+ panList[panCount++] = nXSector;
+ }
+ }
+ for (int i = 0; i < numwalls; i++)
+ {
+ int nXWall = wall[i].extra;
+ if (nXWall > 0)
+ {
+ XWALL *pXWall = &xwall[nXWall];
+ if (pXWall->panXVel || pXWall->panYVel)
+ wallPanList[wallPanCount++] = nXWall;
+ }
+ }
+}
+
+class CSectorListMgr
+{
+public:
+ CSectorListMgr();
+ int CreateList(short);
+ void AddSector(int, short);
+ int GetSectorCount(int);
+ short *GetSectorList(int);
+private:
+ int nLists;
+ int nListSize[32];
+ int nListStart[32];
+ short nSectors[kMaxSectors];
+};
+
+CSectorListMgr::CSectorListMgr()
+{
+ nLists = 0;
+}
+
+int CSectorListMgr::CreateList(short nSector)
+{
+ int nStart = 0;
+ if (nLists)
+ nStart = nListStart[nLists-1]+nListStart[nLists-1];
+ int nList = nLists;
+ nListStart[nList] = nStart;
+ nListSize[nList] = 1;
+ nLists++;
+ short *pList = GetSectorList(nList);
+ pList[0] = nSector;
+ return nList;
+}
+
+void CSectorListMgr::AddSector(int nList, short nSector)
+{
+ for (int i = nLists; i > nList; i--)
+ {
+ short *pList = GetSectorList(i);
+ int nCount = GetSectorCount(i);
+ memmove(pList+1,pList,nCount*sizeof(short));
+ nListStart[i]++;
+ }
+ short *pList = GetSectorList(nList);
+ int nCount = GetSectorCount(nList);
+ pList[nCount] = nSector;
+ nListSize[nList]++;
+}
+
+int CSectorListMgr::GetSectorCount(int nList)
+{
+ return nListSize[nList];
+}
+
+short * CSectorListMgr::GetSectorList(int nList)
+{
+ return nSectors+nListStart[nList];
+}
diff --git a/source/blood/src/sectorfx.h b/source/blood/src/sectorfx.h
new file mode 100644
index 000000000..b1ed0979a
--- /dev/null
+++ b/source/blood/src/sectorfx.h
@@ -0,0 +1,28 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+void DoSectorLighting(void);
+void UndoSectorLighting(void);
+void DoSectorPanning(void);
+void InitSectorFX(void);
\ No newline at end of file
diff --git a/source/blood/src/seq.cpp b/source/blood/src/seq.cpp
new file mode 100644
index 000000000..2c15e25a0
--- /dev/null
+++ b/source/blood/src/seq.cpp
@@ -0,0 +1,598 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include "build.h"
+#include "common_game.h"
+
+#include "blood.h"
+#include "db.h"
+#include "eventq.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "sfx.h"
+#include "sound.h"
+#include "seq.h"
+#include "gameutil.h"
+#include "actor.h"
+#include "tile.h"
+
+#define kMaxClients 256
+#define kMaxSequences 1024
+
+static ACTIVE activeList[kMaxSequences];
+static int activeCount = 0;
+static int nClients = 0;
+static void(*clientCallback[kMaxClients])(int, int);
+
+int seqRegisterClient(void(*pClient)(int, int))
+{
+ dassert(nClients < kMaxClients);
+ clientCallback[nClients] = pClient;
+ return nClients++;
+}
+
+void Seq::Preload(void)
+{
+ if (memcmp(signature, "SEQ\x1a", 4) != 0)
+ ThrowError("Invalid sequence");
+ if ((version & 0xff00) != 0x300)
+ ThrowError("Obsolete sequence version");
+ for (int i = 0; i < nFrames; i++)
+ tilePreloadTile(seqGetTile(&frames[i]));
+}
+
+void Seq::Precache(void)
+{
+ if (memcmp(signature, "SEQ\x1a", 4) != 0)
+ ThrowError("Invalid sequence");
+ if ((version & 0xff00) != 0x300)
+ ThrowError("Obsolete sequence version");
+ for (int i = 0; i < nFrames; i++)
+ tilePrecacheTile(seqGetTile(&frames[i]));
+}
+
+void seqPrecacheId(int id)
+{
+ DICTNODE *hSeq = gSysRes.Lookup(id, "SEQ");
+ if (!hSeq)
+ return;
+ Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
+ pSeq->Precache();
+ gSysRes.Unlock(hSeq);
+}
+
+SEQINST siWall[kMaxXWalls];
+SEQINST siCeiling[kMaxXSectors];
+SEQINST siFloor[kMaxXSectors];
+SEQINST siSprite[kMaxXSprites];
+SEQINST siMasked[kMaxXWalls];
+
+void UpdateSprite(int nXSprite, SEQFRAME *pFrame)
+{
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ int nSprite = xsprite[nXSprite].reference;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ dassert(pSprite->extra == nXSprite);
+ if (pSprite->hitag & 2)
+ {
+ if (tilesiz[pSprite->picnum].y != tilesiz[seqGetTile(pFrame)].y || picanm[pSprite->picnum].yofs != picanm[seqGetTile(pFrame)].yofs
+ || (pFrame->at3_0 && pFrame->at3_0 != pSprite->yrepeat))
+ pSprite->hitag |= 4;
+ }
+ pSprite->picnum = seqGetTile(pFrame);
+ if (pFrame->at5_0)
+ pSprite->pal = pFrame->at5_0;
+ pSprite->shade = pFrame->at4_0;
+
+ int scale = xsprite[nXSprite].scale; // SEQ size scaling
+ if (pFrame->at2_0) {
+ if (scale < 0) pSprite->xrepeat = pFrame->at2_0 / abs(scale);
+ else if (scale > 0) pSprite->xrepeat = pFrame->at2_0 * scale;
+ else pSprite->xrepeat = pFrame->at2_0;
+ }
+
+ if (pFrame->at3_0) {
+ if (scale < 0) pSprite->yrepeat = pFrame->at3_0 / abs(scale);
+ else if (scale > 0) pSprite->yrepeat = pFrame->at3_0 * scale;
+ else pSprite->yrepeat = pFrame->at3_0;
+ }
+
+ if (pFrame->at1_4)
+ pSprite->cstat |= 2;
+ else
+ pSprite->cstat &= ~2;
+ if (pFrame->at1_5)
+ pSprite->cstat |= 512;
+ else
+ pSprite->cstat &= ~512;
+ if (pFrame->at1_6)
+ pSprite->cstat |= 1;
+ else
+ pSprite->cstat &= ~1;
+ if (pFrame->at1_7)
+ pSprite->cstat |= 256;
+ else
+ pSprite->cstat &= ~256;
+ if (pFrame->at6_2)
+ pSprite->cstat |= 32768;
+ else
+ pSprite->cstat &= (unsigned short)~32768;
+ if (pFrame->at6_0)
+ pSprite->cstat |= 4096;
+ else
+ pSprite->cstat &= ~4096;
+ if (pFrame->at5_6)
+ pSprite->hitag |= 256;
+ else
+ pSprite->hitag &= ~256;
+ if (pFrame->at5_7)
+ pSprite->hitag |= 8;
+ else
+ pSprite->hitag &= ~8;
+ if (pFrame->at6_3)
+ pSprite->hitag |= 1024;
+ else
+ pSprite->hitag &= ~1024;
+ if (pFrame->at6_4)
+ pSprite->hitag |= 2048;
+ else
+ pSprite->hitag &= ~2048;
+}
+
+void UpdateWall(int nXWall, SEQFRAME *pFrame)
+{
+ dassert(nXWall > 0 && nXWall < kMaxXWalls);
+ int nWall = xwall[nXWall].reference;
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ walltype *pWall = &wall[nWall];
+ dassert(pWall->extra == nXWall);
+ pWall->picnum = seqGetTile(pFrame);
+ if (pFrame->at5_0)
+ pWall->pal = pFrame->at5_0;
+ if (pFrame->at1_4)
+ pWall->cstat |= 128;
+ else
+ pWall->cstat &= ~128;
+ if (pFrame->at1_5)
+ pWall->cstat |= 512;
+ else
+ pWall->cstat &= ~512;
+ if (pFrame->at1_6)
+ pWall->cstat |= 1;
+ else
+ pWall->cstat &= ~1;
+ if (pFrame->at1_7)
+ pWall->cstat |= 64;
+ else
+ pWall->cstat &= ~64;
+}
+
+void UpdateMasked(int nXWall, SEQFRAME *pFrame)
+{
+ dassert(nXWall > 0 && nXWall < kMaxXWalls);
+ int nWall = xwall[nXWall].reference;
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ walltype *pWall = &wall[nWall];
+ dassert(pWall->extra == nXWall);
+ dassert(pWall->nextwall >= 0);
+ walltype *pWallNext = &wall[pWall->nextwall];
+ pWall->overpicnum = pWallNext->overpicnum = seqGetTile(pFrame);
+ if (pFrame->at5_0)
+ pWall->pal = pWallNext->pal = pFrame->at5_0;
+ if (pFrame->at1_4)
+ {
+ pWall->cstat |= 128;
+ pWallNext->cstat |= 128;
+ }
+ else
+ {
+ pWall->cstat &= ~128;
+ pWallNext->cstat &= ~128;
+ }
+ if (pFrame->at1_5)
+ {
+ pWall->cstat |= 512;
+ pWallNext->cstat |= 512;
+ }
+ else
+ {
+ pWall->cstat &= ~512;
+ pWallNext->cstat &= ~512;
+ }
+ if (pFrame->at1_6)
+ {
+ pWall->cstat |= 1;
+ pWallNext->cstat |= 1;
+ }
+ else
+ {
+ pWall->cstat &= ~1;
+ pWallNext->cstat &= ~1;
+ }
+ if (pFrame->at1_7)
+ {
+ pWall->cstat |= 64;
+ pWallNext->cstat |= 64;
+ }
+ else
+ {
+ pWall->cstat &= ~64;
+ pWallNext->cstat &= ~64;
+ }
+}
+
+void UpdateFloor(int nXSector, SEQFRAME *pFrame)
+{
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ int nSector = xsector[nXSector].reference;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype *pSector = §or[nSector];
+ dassert(pSector->extra == nXSector);
+ pSector->floorpicnum = seqGetTile(pFrame);
+ pSector->floorshade = pFrame->at4_0;
+ if (pFrame->at5_0)
+ pSector->floorpal = pFrame->at5_0;
+}
+
+void UpdateCeiling(int nXSector, SEQFRAME *pFrame)
+{
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ int nSector = xsector[nXSector].reference;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype *pSector = §or[nSector];
+ dassert(pSector->extra == nXSector);
+ pSector->ceilingpicnum = seqGetTile(pFrame);
+ pSector->ceilingshade = pFrame->at4_0;
+ if (pFrame->at5_0)
+ pSector->ceilingpal = pFrame->at5_0;
+}
+
+void SEQINST::Update(ACTIVE *pActive)
+{
+ dassert(frameIndex < pSequence->nFrames);
+ switch (pActive->type)
+ {
+ case 0:
+ UpdateWall(pActive->xindex, &pSequence->frames[frameIndex]);
+ break;
+ case 1:
+ UpdateCeiling(pActive->xindex , &pSequence->frames[frameIndex]);
+ break;
+ case 2:
+ UpdateFloor(pActive->xindex, &pSequence->frames[frameIndex]);
+ break;
+ case 3:
+ {
+
+ UpdateSprite(pActive->xindex, &pSequence->frames[frameIndex]);
+ if (pSequence->frames[frameIndex].at6_1)
+ sfxPlay3DSound(&sprite[xsprite[pActive->xindex].reference], pSequence->ata + Random(pSequence->frames[frameIndex].soundRange), -1, 0);
+
+ spritetype* pSprite = &sprite[xsprite[pActive->xindex].reference];
+ if (pSequence->frames[frameIndex].surfaceSound && zvel[pSprite->xvel] == 0 && xvel[pSprite->xvel] != 0) {
+
+ // by NoOne: add surfaceSound trigger feature
+ if (gUpperLink[pSprite->sectnum] >= 0) break; // don't play surface sound for stacked sectors
+ int surf = tileGetSurfType(pSprite->sectnum + 0x4000); if (!surf) break;
+ static int surfSfxMove[15][4] = {
+ /* {snd1, snd2, gameVolume, myVolume} */
+ {800,801,80,25},
+ {802,803,80,25},
+ {804,805,80,25},
+ {806,807,80,25},
+ {808,809,80,25},
+ {810,811,80,25},
+ {812,813,80,25},
+ {814,815,80,25},
+ {816,817,80,25},
+ {818,819,80,25},
+ {820,821,80,25},
+ {822,823,80,25},
+ {824,825,80,25},
+ {826,827,80,25},
+ {828,829,80,25},
+ };
+
+ int sndId = surfSfxMove[surf][Random(2)];
+ DICTNODE * hRes = gSoundRes.Lookup(sndId, "SFX"); SFX * pEffect = (SFX*)gSoundRes.Load(hRes);
+ sfxPlay3DSoundCP(pSprite, sndId, -1, 0, 0, (surfSfxMove[surf][2] != pEffect->relVol) ? pEffect->relVol : surfSfxMove[surf][3]);
+ }
+
+
+ break;
+ }
+ case 4:
+ UpdateMasked(pActive->xindex, &pSequence->frames[frameIndex]);
+ break;
+ }
+ if (pSequence->frames[frameIndex].at5_5 && atc != -1)
+ clientCallback[atc](pActive->type, pActive->xindex);
+}
+
+SEQINST * GetInstance(int a1, int a2)
+{
+ switch (a1)
+ {
+ case 0:
+ if (a2 > 0 && a2 < kMaxXWalls) return &siWall[a2];
+ break;
+ case 1:
+ if (a2 > 0 && a2 < kMaxXSectors) return &siCeiling[a2];
+ break;
+ case 2:
+ if (a2 > 0 && a2 < kMaxXSectors) return &siFloor[a2];
+ break;
+ case 3:
+ if (a2 > 0 && a2 < kMaxXSprites) return &siSprite[a2];
+ break;
+ case 4:
+ if (a2 > 0 && a2 < kMaxWalls) return &siMasked[a2];
+ break;
+ }
+ return NULL;
+}
+
+void UnlockInstance(SEQINST *pInst)
+{
+ dassert(pInst != NULL);
+ dassert(pInst->hSeq != NULL);
+ dassert(pInst->pSequence != NULL);
+ gSysRes.Unlock(pInst->hSeq);
+ pInst->hSeq = NULL;
+ pInst->pSequence = NULL;
+ pInst->at13 = 0;
+}
+
+void seqSpawn(int a1, int a2, int a3, int a4)
+{
+ SEQINST *pInst = GetInstance(a2, a3);
+ if (!pInst)
+ return;
+ DICTNODE *hSeq = gSysRes.Lookup(a1, "SEQ");
+ if (!hSeq)
+ ThrowError("Missing sequence #%d", a1);
+ int i = activeCount;
+ if (pInst->at13)
+ {
+ if (hSeq == pInst->hSeq)
+ return;
+ UnlockInstance(pInst);
+ for (i = 0; i < activeCount; i++)
+ {
+ if (activeList[i].type == a2 && activeList[i].xindex == a3)
+ break;
+ }
+ dassert(i < activeCount);
+ }
+ Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
+ if (memcmp(pSeq->signature, "SEQ\x1a", 4) != 0)
+ ThrowError("Invalid sequence %d", a1);
+ if ((pSeq->version & 0xff00) != 0x300)
+ ThrowError("Sequence %d is obsolete version", a1);
+ if ((pSeq->version & 0xff) == 0x00)
+ {
+ for (int i = 0; i < pSeq->nFrames; i++)
+ pSeq->frames[i].tile2 = 0;
+ }
+ pInst->at13 = 1;
+ pInst->hSeq = hSeq;
+ pInst->pSequence = pSeq;
+ pInst->at8 = a1;
+ pInst->atc = a4;
+ pInst->at10 = pSeq->at8;
+ pInst->frameIndex = 0;
+ if (i == activeCount)
+ {
+ dassert(activeCount < kMaxSequences);
+ activeList[activeCount].type = a2;
+ activeList[activeCount].xindex = a3;
+ activeCount++;
+ }
+ pInst->Update(&activeList[i]);
+}
+
+void seqKill(int a1, int a2)
+{
+ SEQINST *pInst = GetInstance(a1, a2);
+ if (!pInst || !pInst->at13)
+ return;
+ int i;
+ for (i = 0; i < activeCount; i++)
+ {
+ if (activeList[i].type == a1 && activeList[i].xindex == a2)
+ break;
+ }
+ dassert(i < activeCount);
+ activeCount--;
+ activeList[i] = activeList[activeCount];
+ pInst->at13 = 0;
+ UnlockInstance(pInst);
+}
+
+void seqKillAll(void)
+{
+ for (int i = 0; i < kMaxXWalls; i++)
+ {
+ if (siWall[i].at13)
+ UnlockInstance(&siWall[i]);
+ if (siMasked[i].at13)
+ UnlockInstance(&siMasked[i]);
+ }
+ for (int i = 0; i < kMaxXSectors; i++)
+ {
+ if (siCeiling[i].at13)
+ UnlockInstance(&siCeiling[i]);
+ if (siFloor[i].at13)
+ UnlockInstance(&siFloor[i]);
+ }
+ for (int i = 0; i < kMaxXSprites; i++)
+ {
+ if (siSprite[i].at13)
+ UnlockInstance(&siSprite[i]);
+ }
+ activeCount = 0;
+}
+
+int seqGetStatus(int a1, int a2)
+{
+ SEQINST *pInst = GetInstance(a1, a2);
+ if (pInst && pInst->at13)
+ return pInst->frameIndex;
+ return -1;
+}
+
+int seqGetID(int a1, int a2)
+{
+ SEQINST *pInst = GetInstance(a1, a2);
+ if (pInst)
+ return pInst->at8;
+ return -1;
+}
+
+void seqProcess(int a1)
+{
+ for (int i = 0; i < activeCount; i++)
+ {
+ SEQINST *pInst = GetInstance(activeList[i].type, activeList[i].xindex);
+ Seq *pSeq = pInst->pSequence;
+ dassert(pInst->frameIndex < pSeq->nFrames);
+ pInst->at10 -= a1;
+ while (pInst->at10 < 0)
+ {
+ pInst->at10 += pSeq->at8;
+ pInst->frameIndex++;
+ if (pInst->frameIndex == pSeq->nFrames)
+ {
+ if (pSeq->atc & 1)
+ pInst->frameIndex = 0;
+ else
+ {
+ UnlockInstance(pInst);
+ if (pSeq->atc & 2)
+ {
+ switch (activeList[i].type)
+ {
+ case 3:
+ {
+ int nXSprite = activeList[i].xindex;
+ int nSprite = xsprite[nXSprite].reference;
+ dassert(nSprite >= 0 && nSprite < kMaxSprites);
+ evKill(nSprite, 3);
+ if ((sprite[nSprite].hitag & 16) && sprite[nSprite].zvel >= 200 && sprite[nSprite].zvel < 254)
+ evPost(nSprite, 3, gGameOptions.nMonsterSettings, (COMMAND_ID)9);
+ else
+ DeleteSprite(nSprite);
+ break;
+ }
+ case 4:
+ {
+ int nXWall = activeList[i].xindex;
+ int nWall = xwall[nXWall].reference;
+ dassert(nWall >= 0 && nWall < kMaxWalls);
+ wall[nWall].cstat &= ~(8 + 16 + 32);
+ if (wall[nWall].nextwall != -1)
+ wall[wall[nWall].nextwall].cstat &= ~(8 + 16 + 32);
+ break;
+ }
+ }
+ }
+ activeList[i--] = activeList[--activeCount];
+ break;
+ }
+ }
+ pInst->Update(&activeList[i]);
+ }
+ }
+}
+
+class SeqLoadSave : public LoadSave {
+ virtual void Load(void);
+ virtual void Save(void);
+};
+
+void SeqLoadSave::Load(void)
+{
+ Read(&siWall, sizeof(siWall));
+ Read(&siMasked, sizeof(siMasked));
+ Read(&siCeiling, sizeof(siCeiling));
+ Read(&siFloor, sizeof(siFloor));
+ Read(&siSprite, sizeof(siSprite));
+ Read(&activeList, sizeof(activeList));
+ Read(&activeCount, sizeof(activeCount));
+ for (int i = 0; i < kMaxXWalls; i++)
+ {
+ siWall[i].hSeq = NULL;
+ siMasked[i].hSeq = NULL;
+ siWall[i].pSequence = NULL;
+ siMasked[i].pSequence = NULL;
+ }
+ for (int i = 0; i < kMaxXSectors; i++)
+ {
+ siCeiling[i].hSeq = NULL;
+ siFloor[i].hSeq = NULL;
+ siCeiling[i].pSequence = NULL;
+ siFloor[i].pSequence = NULL;
+ }
+ for (int i = 0; i < kMaxXSprites; i++)
+ {
+ siSprite[i].hSeq = NULL;
+ siSprite[i].pSequence = NULL;
+ }
+ for (int i = 0; i < activeCount; i++)
+ {
+ SEQINST *pInst = GetInstance(activeList[i].type, activeList[i].xindex);
+ if (pInst->at13)
+ {
+ int nSeq = pInst->at8;
+ DICTNODE *hSeq = gSysRes.Lookup(nSeq, "SEQ");
+ if (!hSeq)
+ ThrowError("Missing sequence #%d", nSeq);
+ Seq *pSeq = (Seq*)gSysRes.Lock(hSeq);
+ if (memcmp(pSeq->signature, "SEQ\x1a", 4) != 0)
+ ThrowError("Invalid sequence %d", nSeq);
+ if ((pSeq->version & 0xff00) != 0x300)
+ ThrowError("Sequence %d is obsolete version", nSeq);
+ pInst->hSeq = hSeq;
+ pInst->pSequence = pSeq;
+ }
+ }
+}
+
+void SeqLoadSave::Save(void)
+{
+ Write(&siWall, sizeof(siWall));
+ Write(&siMasked, sizeof(siMasked));
+ Write(&siCeiling, sizeof(siCeiling));
+ Write(&siFloor, sizeof(siFloor));
+ Write(&siSprite, sizeof(siSprite));
+ Write(&activeList, sizeof(activeList));
+ Write(&activeCount, sizeof(activeCount));
+}
+
+static SeqLoadSave *myLoadSave;
+
+void SeqLoadSaveConstruct(void)
+{
+ myLoadSave = new SeqLoadSave();
+}
diff --git a/source/blood/src/seq.h b/source/blood/src/seq.h
new file mode 100644
index 000000000..d62e6ceaa
--- /dev/null
+++ b/source/blood/src/seq.h
@@ -0,0 +1,94 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "resource.h"
+
+struct SEQFRAME {
+ unsigned int tile : 12;
+ unsigned int at1_4 : 1; // transparent
+ unsigned int at1_5 : 1; // transparent
+ unsigned int at1_6 : 1; // blockable
+ unsigned int at1_7 : 1; // hittable
+ unsigned int at2_0 : 8; // xrepeat
+ unsigned int at3_0 : 8; // yrepeat
+ signed int at4_0 : 8; // shade
+ unsigned int at5_0 : 5; // palette
+ unsigned int at5_5 : 1; //
+ unsigned int at5_6 : 1; //
+ unsigned int at5_7 : 1; //
+ unsigned int at6_0 : 1; //
+ unsigned int at6_1 : 1; //
+ unsigned int at6_2 : 1; // invisible
+ unsigned int at6_3 : 1; //
+ unsigned int at6_4 : 1; //
+ unsigned int tile2 : 4;
+ unsigned soundRange : 4; // (by NoOne) random sound range relative to global SEQ sound
+ unsigned surfaceSound : 1; // (by NoOne) trigger surface sound when moving / touching
+ unsigned reserved : 2;
+};
+
+struct Seq {
+ char signature[4];
+ short version;
+ short nFrames; // at6
+ short at8;
+ short ata;
+ int atc;
+ SEQFRAME frames[1]; // at10
+ void Preload(void);
+ void Precache(void);
+};
+
+struct ACTIVE
+{
+ unsigned char type;
+ unsigned short xindex;
+};
+
+struct SEQINST
+{
+ DICTNODE *hSeq;
+ Seq *pSequence; // mass
+ int at8;
+ int atc;
+ short at10;
+ unsigned char frameIndex; // at12
+ char at13;
+ void Update(ACTIVE *pActive);
+};
+
+inline int seqGetTile(SEQFRAME* pFrame)
+{
+ return pFrame->tile+(pFrame->tile2<<12);
+}
+
+int seqRegisterClient(void(*pClient)(int, int));
+void seqPrecacheId(int id);
+SEQINST * GetInstance(int a1, int a2);
+void UnlockInstance(SEQINST *pInst);
+void seqSpawn(int a1, int a2, int a3, int a4 = -1);
+void seqKill(int a1, int a2);
+void seqKillAll(void);
+int seqGetStatus(int a1, int a2);
+int seqGetID(int a1, int a2);
+void seqProcess(int a1);
\ No newline at end of file
diff --git a/source/blood/src/sfx.cpp b/source/blood/src/sfx.cpp
new file mode 100644
index 000000000..c861481ec
--- /dev/null
+++ b/source/blood/src/sfx.cpp
@@ -0,0 +1,528 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include "build.h"
+#include "compat.h"
+#include "common_game.h"
+#include "fx_man.h"
+
+#include "config.h"
+#include "gameutil.h"
+#include "player.h"
+#include "resource.h"
+#include "sfx.h"
+#include "sound.h"
+#include "trig.h"
+
+POINT2D earL, earR, earL0, earR0; // Ear position
+VECTOR2D earVL, earVR; // Ear velocity ?
+int lPhase, rPhase, lVol, rVol, lPitch, rPitch;
+
+struct BONKLE
+{
+ int at0;
+ int at4;
+ DICTNODE *at8;
+ int atc;
+ spritetype *at10;
+ int at14;
+ int at18;
+ int at1c;
+ POINT3D at20;
+ POINT3D at2c;
+ //int at20;
+ //int at24;
+ //int at28;
+ //int at2c;
+ //int at30;
+ //int at34;
+ int at38;
+ int at3c;
+};
+
+BONKLE Bonkle[256];
+BONKLE *BonkleCache[256];
+
+int nBonkles;
+
+void sfxInit(void)
+{
+ for (int i = 0; i < 256; i++)
+ BonkleCache[i] = &Bonkle[i];
+ nBonkles = 0;
+}
+
+void sfxTerm()
+{
+}
+
+int Vol3d(int angle, int dist)
+{
+ return dist - mulscale16(dist, 0x2000 - mulscale30(0x2000, Cos(angle)));
+}
+
+void Calc3DValues(BONKLE *pBonkle)
+{
+ int dx = pBonkle->at20.x - gMe->pSprite->x;
+ int dy = pBonkle->at20.y - gMe->pSprite->y;
+ int dz = pBonkle->at20.z - gMe->pSprite->z;
+ int angle = getangle(dx, dy);
+ dx >>= 4;
+ dy >>= 4;
+ dz >>= 8;
+ int distance = ksqrt(dx*dx + dy * dy + dz * dz);
+ distance = ClipLow((distance >> 2) + (distance >> 3), 64);
+ int v14, v18;
+ v14 = v18 = scale(pBonkle->at1c, 80, distance);
+ int sinVal = Sin(angle);
+ int cosVal = Cos(angle);
+ int v8 = dmulscale30r(cosVal, pBonkle->at20.x - pBonkle->at2c.x, sinVal, pBonkle->at20.y - pBonkle->at2c.y);
+
+ int distanceL = approxDist(pBonkle->at20.x - earL.x, pBonkle->at20.y - earL.y);
+ lVol = Vol3d(angle - (gMe->pSprite->ang - 85), v18);
+ int phaseLeft = mulscale16r(distanceL, pBonkle->at3c == 1 ? 4114 : 8228);
+ lPitch = scale(pBonkle->at18, dmulscale30r(cosVal, earVL.dx, sinVal, earVL.dy) + 5853, v8 + 5853);
+
+ int distanceR = approxDist(pBonkle->at20.x - earR.x, pBonkle->at20.y - earR.y);
+ rVol = Vol3d(angle - (gMe->pSprite->ang + 85), v14);
+ int phaseRight = mulscale16r(distanceR, pBonkle->at3c == 1 ? 4114 : 8228);
+ rPitch = scale(pBonkle->at18, dmulscale30r(cosVal, earVR.dx, sinVal, earVR.dy) + 5853, v8 + 5853);
+
+ int phaseMin = ClipHigh(phaseLeft, phaseRight);
+ lPhase = phaseRight - phaseMin;
+ rPhase = phaseLeft - phaseMin;
+}
+
+void sfxPlay3DSound(int x, int y, int z, int soundId, int nSector)
+{
+ if (!SoundToggle)
+ return;
+ if (soundId < 0)
+ ThrowError("Invalid sound ID");
+ DICTNODE *hRes = gSoundRes.Lookup(soundId, "SFX");
+ if (!hRes)
+ return;
+
+ SFX *pEffect = (SFX*)gSoundRes.Load(hRes);
+ hRes = gSoundRes.Lookup(pEffect->rawName, "RAW");
+ if (!hRes)
+ return;
+ int v1c, v18;
+ v1c = v18 = mulscale16(pEffect->pitch, sndGetRate(pEffect->format));
+ if (nBonkles >= 256)
+ return;
+ BONKLE *pBonkle = BonkleCache[nBonkles++];
+ pBonkle->at10 = NULL;
+ pBonkle->at20.x = x;
+ pBonkle->at20.y = y;
+ pBonkle->at20.z = z;
+ pBonkle->at38 = nSector;
+ FindSector(x, y, z, &pBonkle->at38);
+ pBonkle->at2c = pBonkle->at20;
+ pBonkle->atc = soundId;
+ pBonkle->at8 = hRes;
+ pBonkle->at1c = pEffect->relVol;
+ pBonkle->at18 = v18;
+ pBonkle->at3c = pEffect->format;
+ int size = hRes->size;
+ char *pData = (char*)gSoundRes.Lock(hRes);
+ Calc3DValues(pBonkle);
+ int priority = 1;
+ if (priority < lVol)
+ priority = lVol;
+ if (priority < rVol)
+ priority = rVol;
+ if (gDoppler)
+ {
+ DisableInterrupts();
+ pBonkle->at0 = FX_PlayRaw(pData + lPhase, size - lPhase, lPitch, 0, lVol, lVol, 0, priority, 1.f, (intptr_t)&pBonkle->at0);
+ pBonkle->at4 = FX_PlayRaw(pData + rPhase, size - rPhase, rPitch, 0, rVol, 0, rVol, priority, 1.f, (intptr_t)&pBonkle->at4);
+ RestoreInterrupts();
+ }
+ else
+ {
+ pBonkle->at0 = FX_PlayRaw(pData + lPhase, size - lPhase, v1c, 0, lVol, lVol, rVol, priority, 1.f, (intptr_t)&pBonkle->at0);
+ pBonkle->at4 = 0;
+ }
+}
+
+void sfxPlay3DSound(spritetype *pSprite, int soundId, int a3, int a4)
+{
+ if (!SoundToggle)
+ return;
+ if (!pSprite)
+ return;
+ if (soundId < 0)
+ return;
+ DICTNODE *hRes = gSoundRes.Lookup(soundId, "SFX");
+ if (!hRes)
+ return;
+
+ SFX *pEffect = (SFX*)gSoundRes.Load(hRes);
+ hRes = gSoundRes.Lookup(pEffect->rawName, "RAW");
+ if (!hRes)
+ return;
+ int size = hRes->size;
+ if (size <= 0)
+ return;
+ int v14;
+ v14 = mulscale16(pEffect->pitch, sndGetRate(pEffect->format));
+ BONKLE *pBonkle = NULL;
+ if (a3 >= 0)
+ {
+ int i;
+ for (i = 0; i < nBonkles; i++)
+ {
+ pBonkle = BonkleCache[i];
+ if (pBonkle->at14 == a3 && (pBonkle->at10 == pSprite || (a4 & 1) != 0))
+ {
+ if ((a4 & 4) != 0 && pBonkle->at14 == a3)
+ return;
+ if ((a4 & 2) != 0 && pBonkle->atc == soundId)
+ return;
+ if (pBonkle->at0 > 0)
+ FX_StopSound(pBonkle->at0);
+ if (pBonkle->at4 > 0)
+ FX_StopSound(pBonkle->at4);
+ if (pBonkle->at8)
+ {
+ gSoundRes.Unlock(pBonkle->at8);
+ pBonkle->at8 = NULL;
+ }
+ break;
+ }
+ }
+ if (i == nBonkles)
+ {
+ if (nBonkles >= 256)
+ return;
+ pBonkle = BonkleCache[nBonkles++];
+ }
+ pBonkle->at10 = pSprite;
+ pBonkle->at14 = a3;
+ }
+ else
+ {
+ if (nBonkles >= 256)
+ return;
+ pBonkle = BonkleCache[nBonkles++];
+ pBonkle->at10 = NULL;
+ }
+ pBonkle->at20.x = pSprite->x;
+ pBonkle->at20.y = pSprite->y;
+ pBonkle->at20.z = pSprite->z;
+ pBonkle->at38 = pSprite->sectnum;
+ pBonkle->at2c = pBonkle->at20;
+ pBonkle->atc = soundId;
+ pBonkle->at8 = hRes;
+ pBonkle->at1c = pEffect->relVol;
+ pBonkle->at18 = v14;
+ Calc3DValues(pBonkle);
+ int priority = 1;
+ if (priority < lVol)
+ priority = lVol;
+ if (priority < rVol)
+ priority = rVol;
+ int loopStart = pEffect->loopStart;
+ int loopEnd = ClipLow(size - 1, 0);
+ if (a3 < 0)
+ loopStart = -1;
+ DisableInterrupts();
+ char *pData = (char*)gSoundRes.Lock(hRes);
+ if (loopStart >= 0)
+ {
+ if (gDoppler)
+ {
+ pBonkle->at0 = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, lPitch, 0, lVol, lVol, 0, priority, 1.f, (intptr_t)&pBonkle->at0);
+ pBonkle->at4 = FX_PlayLoopedRaw(pData + rPhase, size - rPhase, pData + loopStart, pData + loopEnd, rPitch, 0, rVol, 0, rVol, priority, 1.f, (intptr_t)&pBonkle->at4);
+ }
+ else
+ {
+ pBonkle->at0 = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, v14, 0, lVol, lVol, rVol, priority, 1.f, (intptr_t)&pBonkle->at0);
+ pBonkle->at4 = 0;
+ }
+ }
+ else
+ {
+ pData = (char*)gSoundRes.Lock(pBonkle->at8);
+ if (gDoppler)
+ {
+ pBonkle->at0 = FX_PlayRaw(pData + lPhase, size - lPhase, lPitch, 0, lVol, lVol, 0, priority, 1.f, (intptr_t)&pBonkle->at0);
+ pBonkle->at4 = FX_PlayRaw(pData + rPhase, size - rPhase, rPitch, 0, rVol, 0, rVol, priority, 1.f, (intptr_t)&pBonkle->at4);
+ }
+ else
+ {
+ pBonkle->at0 = FX_PlayRaw(pData + lPhase, size - lPhase, v14, 0, lVol, lVol, rVol, priority, 1.f, (intptr_t)&pBonkle->at0);
+ pBonkle->at4 = 0;
+ }
+ }
+ RestoreInterrupts();
+}
+
+// By NoOne: same as previous, but allows to set custom pitch for sound AND volume. Used by SFX gen now.
+void sfxPlay3DSoundCP(spritetype* pSprite, int soundId, int a3, int a4, int pitch, int volume)
+{
+ if (!SoundToggle || !pSprite || soundId < 0) return;
+ DICTNODE* hRes = gSoundRes.Lookup(soundId, "SFX");
+ if (!hRes) return;
+
+ SFX* pEffect = (SFX*)gSoundRes.Load(hRes);
+ hRes = gSoundRes.Lookup(pEffect->rawName, "RAW");
+ if (!hRes) return;
+ int size = hRes->size;
+ if (size <= 0) return;
+
+ if (pitch <= 0) pitch = pEffect->pitch;
+ else pitch -= Random(pEffect->pitchRange);
+
+ int v14;
+ v14 = mulscale16(pitch, sndGetRate(pEffect->format));
+
+ BONKLE * pBonkle = NULL;
+ if (a3 >= 0)
+ {
+ int i;
+ for (i = 0; i < nBonkles; i++)
+ {
+ pBonkle = BonkleCache[i];
+ if (pBonkle->at14 == a3 && (pBonkle->at10 == pSprite || (a4 & 1) != 0))
+ {
+ if ((a4 & 4) != 0 && pBonkle->at14 == a3)
+ return;
+ if ((a4 & 2) != 0 && pBonkle->atc == soundId)
+ return;
+ if (pBonkle->at0 > 0)
+ FX_StopSound(pBonkle->at0);
+ if (pBonkle->at4 > 0)
+ FX_StopSound(pBonkle->at4);
+ if (pBonkle->at8)
+ {
+ gSoundRes.Unlock(pBonkle->at8);
+ pBonkle->at8 = NULL;
+ }
+ break;
+ }
+ }
+ if (i == nBonkles)
+ {
+ if (nBonkles >= 256)
+ return;
+ pBonkle = BonkleCache[nBonkles++];
+ }
+ pBonkle->at10 = pSprite;
+ pBonkle->at14 = a3;
+ }
+ else
+ {
+ if (nBonkles >= 256)
+ return;
+ pBonkle = BonkleCache[nBonkles++];
+ pBonkle->at10 = NULL;
+ }
+ pBonkle->at20.x = pSprite->x;
+ pBonkle->at20.y = pSprite->y;
+ pBonkle->at20.z = pSprite->z;
+ pBonkle->at38 = pSprite->sectnum;
+ pBonkle->at2c = pBonkle->at20;
+ pBonkle->atc = soundId;
+ pBonkle->at8 = hRes;
+ pBonkle->at1c = (volume <= 0) ? pEffect->relVol : volume;
+ pBonkle->at18 = v14;
+ Calc3DValues(pBonkle);
+ int priority = 1;
+ if (priority < lVol)
+ priority = lVol;
+ if (priority < rVol)
+ priority = rVol;
+ int loopStart = pEffect->loopStart;
+ int loopEnd = ClipLow(size - 1, 0);
+ if (a3 < 0)
+ loopStart = -1;
+ DisableInterrupts();
+ char* pData = (char*)gSoundRes.Lock(hRes);
+ if (loopStart >= 0)
+ {
+ if (gDoppler)
+ {
+ pBonkle->at0 = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, lPitch, 0, lVol, lVol, 0, priority, 1.f, (intptr_t)& pBonkle->at0);
+ pBonkle->at4 = FX_PlayLoopedRaw(pData + rPhase, size - rPhase, pData + loopStart, pData + loopEnd, rPitch, 0, rVol, 0, rVol, priority, 1.f, (intptr_t)& pBonkle->at4);
+ }
+ else
+ {
+ pBonkle->at0 = FX_PlayLoopedRaw(pData + lPhase, size - lPhase, pData + loopStart, pData + loopEnd, v14, 0, lVol, lVol, rVol, priority, 1.f, (intptr_t)& pBonkle->at0);
+ pBonkle->at4 = 0;
+ }
+ }
+ else
+ {
+ pData = (char*)gSoundRes.Lock(pBonkle->at8);
+ if (gDoppler)
+ {
+ pBonkle->at0 = FX_PlayRaw(pData + lPhase, size - lPhase, lPitch, 0, lVol, lVol, 0, priority, 1.f, (intptr_t)& pBonkle->at0);
+ pBonkle->at4 = FX_PlayRaw(pData + rPhase, size - rPhase, rPitch, 0, rVol, 0, rVol, priority, 1.f, (intptr_t)& pBonkle->at4);
+ }
+ else
+ {
+ pBonkle->at0 = FX_PlayRaw(pData + lPhase, size - lPhase, v14, 0, lVol, lVol, rVol, priority, 1.f, (intptr_t)& pBonkle->at0);
+ pBonkle->at4 = 0;
+ }
+ }
+ RestoreInterrupts();
+}
+
+
+void sfxKill3DSound(spritetype *pSprite, int a2, int a3)
+{
+ if (!pSprite)
+ return;
+ for (int i = nBonkles - 1; i >= 0; i--)
+ {
+ BONKLE *pBonkle = BonkleCache[i];
+ if (pBonkle->at10 == pSprite && (a2 < 0 || a2 == pBonkle->at14) && (a3 < 0 || a3 == pBonkle->atc))
+ {
+ if (pBonkle->at0 > 0)
+ {
+ FX_EndLooping(pBonkle->at0);
+ FX_StopSound(pBonkle->at0);
+ }
+ if (pBonkle->at4 > 0)
+ {
+ FX_EndLooping(pBonkle->at4);
+ FX_StopSound(pBonkle->at4);
+ }
+ if (pBonkle->at8)
+ {
+ gSoundRes.Unlock(pBonkle->at8);
+ pBonkle->at8 = NULL;
+ }
+ BonkleCache[i] = BonkleCache[--nBonkles];
+ BonkleCache[nBonkles] = pBonkle;
+ break;
+ }
+ }
+}
+
+void sfxKillAllSounds(void)
+{
+ for (int i = nBonkles - 1; i >= 0; i--)
+ {
+ BONKLE *pBonkle = BonkleCache[i];
+ if (pBonkle->at0 > 0)
+ {
+ FX_EndLooping(pBonkle->at0);
+ FX_StopSound(pBonkle->at0);
+ }
+ if (pBonkle->at4 > 0)
+ {
+ FX_EndLooping(pBonkle->at4);
+ FX_StopSound(pBonkle->at4);
+ }
+ if (pBonkle->at8)
+ {
+ gSoundRes.Unlock(pBonkle->at8);
+ pBonkle->at8 = NULL;
+ }
+ BonkleCache[i] = BonkleCache[--nBonkles];
+ BonkleCache[nBonkles] = pBonkle;
+ }
+}
+
+void sfxUpdate3DSounds(void)
+{
+ int dx = mulscale30(Cos(gMe->pSprite->ang + 512), 43);
+ earL0 = earL;
+ int dy = mulscale30(Sin(gMe->pSprite->ang + 512), 43);
+ earR0 = earR;
+ earL.x = gMe->pSprite->x - dx;
+ earL.y = gMe->pSprite->y - dy;
+ earR.x = gMe->pSprite->x + dx;
+ earR.y = gMe->pSprite->y + dy;
+ earVL.dx = earL.x - earL0.x;
+ earVL.dy = earL.y - earL0.y;
+ earVR.dx = earR.x - earR0.x;
+ earVR.dy = earR.y - earR0.y;
+ for (int i = nBonkles - 1; i >= 0; i--)
+ {
+ BONKLE *pBonkle = BonkleCache[i];
+ if (pBonkle->at0 > 0 || pBonkle->at4 > 0)
+ {
+ if (!pBonkle->at8)
+ continue;
+ if (pBonkle->at10)
+ {
+ pBonkle->at2c = pBonkle->at20;
+ pBonkle->at20.x = pBonkle->at10->x;
+ pBonkle->at20.y = pBonkle->at10->y;
+ pBonkle->at20.z = pBonkle->at10->z;
+ pBonkle->at38 = pBonkle->at10->sectnum;
+ }
+ Calc3DValues(pBonkle);
+ DisableInterrupts();
+ if (pBonkle->at0 > 0)
+ {
+ if (pBonkle->at4 > 0)
+ {
+ FX_SetPan(pBonkle->at0, lVol, lVol, 0);
+ FX_SetFrequency(pBonkle->at0, lPitch);
+ }
+ else
+ FX_SetPan(pBonkle->at0, lVol, lVol, rVol);
+ }
+ if (pBonkle->at4 > 0)
+ {
+ FX_SetPan(pBonkle->at4, rVol, 0, rVol);
+ FX_SetFrequency(pBonkle->at4, rPitch);
+ }
+ RestoreInterrupts();
+ }
+ else
+ {
+ gSoundRes.Unlock(pBonkle->at8);
+ pBonkle->at8 = NULL;
+ BonkleCache[i] = BonkleCache[--nBonkles];
+ BonkleCache[nBonkles] = pBonkle;
+ }
+ }
+}
+
+void sfxSetReverb(bool toggle)
+{
+ if (toggle)
+ {
+ FX_SetReverb(128);
+ FX_SetReverbDelay(10);
+ }
+ else
+ FX_SetReverb(0);
+}
+
+void sfxSetReverb2(bool toggle)
+{
+ if (toggle)
+ {
+ FX_SetReverb(128);
+ FX_SetReverbDelay(20);
+ }
+ else
+ FX_SetReverb(0);
+}
diff --git a/source/blood/src/sfx.h b/source/blood/src/sfx.h
new file mode 100644
index 000000000..3545118ac
--- /dev/null
+++ b/source/blood/src/sfx.h
@@ -0,0 +1,35 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "db.h"
+
+void sfxInit(void);
+void sfxTerm(void);
+void sfxPlay3DSound(int x, int y, int z, int soundId, int nSector);
+void sfxPlay3DSound(spritetype *pSprite, int soundId, int a3 = -1, int a4 = 0);
+void sfxPlay3DSoundCP(spritetype* pSprite, int soundId, int a3 = -1, int a4 = 0, int pitch = 0, int volume = 0);
+void sfxKill3DSound(spritetype *pSprite, int a2 = -1, int a3 = -1);
+void sfxKillAllSounds(void);
+void sfxUpdate3DSounds(void);
+void sfxSetReverb(bool toggle);
+void sfxSetReverb2(bool toggle);
\ No newline at end of file
diff --git a/source/blood/src/sound.cpp b/source/blood/src/sound.cpp
new file mode 100644
index 000000000..e94448de4
--- /dev/null
+++ b/source/blood/src/sound.cpp
@@ -0,0 +1,511 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#include "music.h"
+#include "fx_man.h"
+#include "common_game.h"
+#include "config.h"
+#include "levels.h"
+#include "resource.h"
+#include "sound.h"
+#include "renderlayer.h"
+
+Resource gSoundRes;
+
+int soundRates[13] = {
+ 11025,
+ 11025,
+ 11025,
+ 11025,
+ 11025,
+ 22050,
+ 22050,
+ 22050,
+ 22050,
+ 44100,
+ 44100,
+ 44100,
+ 44100,
+};
+#define kMaxChannels 32
+
+int sndGetRate(int format)
+{
+ if (format < 13)
+ return soundRates[format];
+ return 11025;
+}
+
+SAMPLE2D Channel[kMaxChannels];
+
+SAMPLE2D * FindChannel(void)
+{
+ for (int i = kMaxChannels - 1; i >= 0; i--)
+ if (Channel[i].at5 == 0)
+ return &Channel[i];
+ ThrowError("No free channel available for sample");
+ return NULL;
+}
+
+DICTNODE *hSong;
+char *pSongPtr;
+int nSongSize;
+bool bWaveMusic;
+int nWaveMusicHandle;
+
+int sndPlaySong(const char *songName, bool bLoop)
+{
+ if (!MusicToggle)
+ return 0;
+ if (!songName || strlen(songName) == 0)
+ return 1;
+
+ int32_t fp = S_OpenAudio(songName, 0, 1);
+ if (EDUKE32_PREDICT_FALSE(fp < 0))
+ {
+ hSong = gSoundRes.Lookup(songName, "MID");
+ if (!hSong)
+ {
+ OSD_Printf(OSD_ERROR "sndPlaySong(): error: can't open \"%s\" for playback!\n", songName);
+ return 2;
+ }
+ int nNewSongSize = hSong->size;
+ char *pNewSongPtr = (char *)Xaligned_alloc(16, nNewSongSize);
+ gSoundRes.Load(hSong, pNewSongPtr);
+ MUSIC_SetVolume(MusicVolume);
+ int32_t retval = MUSIC_PlaySong(pNewSongPtr, nNewSongSize, bLoop);
+
+ if (retval != MUSIC_Ok)
+ {
+ ALIGNED_FREE_AND_NULL(pNewSongPtr);
+ return 5;
+ }
+
+ if (bWaveMusic && nWaveMusicHandle >= 0)
+ {
+ FX_StopSound(nWaveMusicHandle);
+ nWaveMusicHandle = -1;
+ }
+
+ bWaveMusic = false;
+ ALIGNED_FREE_AND_NULL(pSongPtr);
+ pSongPtr = pNewSongPtr;
+ nSongSize = nNewSongSize;
+ return 0;
+ }
+
+ int32_t nSongLen = kfilelength(fp);
+
+ if (EDUKE32_PREDICT_FALSE(nSongLen < 4))
+ {
+ OSD_Printf(OSD_ERROR "sndPlaySong(): error: empty music file \"%s\"\n", songName);
+ kclose(fp);
+ return 3;
+ }
+
+ char * pNewSongPtr = (char *)Xaligned_alloc(16, nSongLen);
+ int nNewSongSize = kread(fp, pNewSongPtr, nSongLen);
+
+ if (EDUKE32_PREDICT_FALSE(nNewSongSize != nSongLen))
+ {
+ OSD_Printf(OSD_ERROR "sndPlaySong(): error: read %d bytes from \"%s\", expected %d\n",
+ nNewSongSize, songName, nSongLen);
+ kclose(fp);
+ ALIGNED_FREE_AND_NULL(pNewSongPtr);
+ return 4;
+ }
+
+ kclose(fp);
+
+ if (!Bmemcmp(pNewSongPtr, "MThd", 4))
+ {
+ int32_t retval = MUSIC_PlaySong(pNewSongPtr, nNewSongSize, bLoop);
+
+ if (retval != MUSIC_Ok)
+ {
+ ALIGNED_FREE_AND_NULL(pNewSongPtr);
+ return 5;
+ }
+
+ if (bWaveMusic && nWaveMusicHandle >= 0)
+ {
+ FX_StopSound(nWaveMusicHandle);
+ nWaveMusicHandle = -1;
+ }
+
+ bWaveMusic = false;
+ ALIGNED_FREE_AND_NULL(pSongPtr);
+ pSongPtr = pNewSongPtr;
+ nSongSize = nNewSongSize;
+ }
+ else
+ {
+ int nNewWaveMusicHandle = FX_Play(pNewSongPtr, bLoop ? nNewSongSize : -1, 0, 0, 0, MusicVolume, MusicVolume, MusicVolume,
+ FX_MUSIC_PRIORITY, 1.f, (intptr_t)&nWaveMusicHandle);
+
+ if (nNewWaveMusicHandle <= FX_Ok)
+ {
+ ALIGNED_FREE_AND_NULL(pNewSongPtr);
+ return 5;
+ }
+
+ if (bWaveMusic && nWaveMusicHandle >= 0)
+ FX_StopSound(nWaveMusicHandle);
+
+ MUSIC_StopSong();
+
+ nWaveMusicHandle = nNewWaveMusicHandle;
+ bWaveMusic = true;
+ ALIGNED_FREE_AND_NULL(pSongPtr);
+ pSongPtr = pNewSongPtr;
+ nSongSize = nNewSongSize;
+ }
+
+ return 0;
+}
+
+int sndTryPlaySpecialMusic(int nMusic)
+{
+ int nEpisode = nMusic/kMaxLevels;
+ int nLevel = nMusic%kMaxLevels;
+ if (!sndPlaySong(gEpisodeInfo[nEpisode].at28[nLevel].atd0, true))
+ {
+ strncpy(gGameOptions.zLevelSong, gEpisodeInfo[nEpisode].at28[nLevel].atd0, BMAX_PATH);
+ return 0;
+ }
+ return 1;
+}
+
+void sndPlaySpecialMusicOrNothing(int nMusic)
+{
+ int nEpisode = nMusic/kMaxLevels;
+ int nLevel = nMusic%kMaxLevels;
+ if (sndTryPlaySpecialMusic(nMusic))
+ {
+ sndStopSong();
+ strncpy(gGameOptions.zLevelSong, gEpisodeInfo[nEpisode].at28[nLevel].atd0, BMAX_PATH);
+ }
+}
+
+bool sndIsSongPlaying(void)
+{
+ //return MUSIC_SongPlaying();
+ return false;
+}
+
+void sndFadeSong(int nTime)
+{
+ UNREFERENCED_PARAMETER(nTime);
+ // NUKE-TODO:
+ //if (MusicDevice == -1)
+ // return;
+ //if (gEightyTwoFifty && sndMultiPlayer)
+ // return;
+ //MUSIC_FadeVolume(0, nTime);
+ if (bWaveMusic && nWaveMusicHandle >= 0)
+ {
+ FX_StopSound(nWaveMusicHandle);
+ nWaveMusicHandle = -1;
+ bWaveMusic = false;
+ }
+ MUSIC_SetVolume(0);
+ MUSIC_StopSong();
+}
+
+void sndSetMusicVolume(int nVolume)
+{
+ MusicVolume = nVolume;
+ if (bWaveMusic && nWaveMusicHandle >= 0)
+ FX_SetPan(nWaveMusicHandle, nVolume, nVolume, nVolume);
+ MUSIC_SetVolume(nVolume);
+}
+
+void sndSetFXVolume(int nVolume)
+{
+ FXVolume = nVolume;
+ FX_SetVolume(nVolume);
+}
+
+void sndStopSong(void)
+{
+ if (bWaveMusic && nWaveMusicHandle >= 0)
+ {
+ FX_StopSound(nWaveMusicHandle);
+ nWaveMusicHandle = -1;
+ bWaveMusic = false;
+ }
+
+ MUSIC_StopSong();
+
+ ALIGNED_FREE_AND_NULL(pSongPtr);
+ nSongSize = 0;
+}
+
+void SoundCallback(intptr_t val)
+{
+ SAMPLE2D *pChannel = (SAMPLE2D*)val;
+ pChannel->at0 = 0;
+}
+
+void sndKillSound(SAMPLE2D *pChannel);
+
+void sndStartSample(const char *pzSound, int nVolume, int nChannel)
+{
+ if (!SoundToggle)
+ return;
+ if (!strlen(pzSound))
+ return;
+ dassert(nChannel >= -1 && nChannel < kMaxChannels);
+ SAMPLE2D *pChannel;
+ if (nChannel == -1)
+ pChannel = FindChannel();
+ else
+ pChannel = &Channel[nChannel];
+ if (pChannel->at0 > 0)
+ sndKillSound(pChannel);
+ pChannel->at5 = gSoundRes.Lookup(pzSound, "RAW");
+ if (!pChannel->at5)
+ return;
+ int nSize = pChannel->at5->size;
+ char *pData = (char*)gSoundRes.Lock(pChannel->at5);
+ pChannel->at0 = FX_PlayRaw(pData, nSize, sndGetRate(1), 0, nVolume, nVolume, nVolume, nVolume, 1.f, (intptr_t)&pChannel->at0);
+}
+
+void sndStartSample(unsigned int nSound, int nVolume, int nChannel, bool bLoop)
+{
+ if (!SoundToggle)
+ return;
+ dassert(nChannel >= -1 && nChannel < kMaxChannels);
+ DICTNODE *hSfx = gSoundRes.Lookup(nSound, "SFX");
+ if (!hSfx)
+ return;
+ SFX *pEffect = (SFX*)gSoundRes.Lock(hSfx);
+ dassert(pEffect != NULL);
+ SAMPLE2D *pChannel;
+ if (nChannel == -1)
+ pChannel = FindChannel();
+ else
+ pChannel = &Channel[nChannel];
+ if (pChannel->at0 > 0)
+ sndKillSound(pChannel);
+ pChannel->at5 = gSoundRes.Lookup(pEffect->rawName, "RAW");
+ if (!pChannel->at5)
+ return;
+ if (nVolume < 0)
+ nVolume = pEffect->relVol;
+ int nSize = pChannel->at5->size;
+ int nLoopEnd = nSize - 1;
+ if (nLoopEnd < 0)
+ nLoopEnd = 0;
+ if (nSize <= 0)
+ return;
+ char *pData = (char*)gSoundRes.Lock(pChannel->at5);
+ if (nChannel < 0)
+ bLoop = false;
+ if (bLoop)
+ {
+ pChannel->at0 = FX_PlayLoopedRaw(pData, nSize, pData + pEffect->loopStart, pData + nLoopEnd, sndGetRate(pEffect->format),
+ 0, nVolume, nVolume, nVolume, nVolume, 1.f, (intptr_t)&pChannel->at0);
+ pChannel->at4 |= 1;
+ }
+ else
+ {
+ pChannel->at0 = FX_PlayRaw(pData, nSize, sndGetRate(pEffect->format), 0, nVolume, nVolume, nVolume, nVolume, 1.f, (intptr_t)&pChannel->at0);
+ pChannel->at4 &= ~1;
+ }
+}
+
+void sndStartWavID(unsigned int nSound, int nVolume, int nChannel)
+{
+ if (!SoundToggle)
+ return;
+ dassert(nChannel >= -1 && nChannel < kMaxChannels);
+ SAMPLE2D *pChannel;
+ if (nChannel == -1)
+ pChannel = FindChannel();
+ else
+ pChannel = &Channel[nChannel];
+ if (pChannel->at0 > 0)
+ sndKillSound(pChannel);
+ pChannel->at5 = gSoundRes.Lookup(nSound, "WAV");
+ if (!pChannel->at5)
+ return;
+ char *pData = (char*)gSoundRes.Lock(pChannel->at5);
+ pChannel->at0 = FX_Play(pData, pChannel->at5->size, 0, -1, 0, nVolume, nVolume, nVolume, nVolume, 1.f, (intptr_t)&pChannel->at0);
+}
+
+void sndKillSound(SAMPLE2D *pChannel)
+{
+ if (pChannel->at4 & 1)
+ {
+ FX_EndLooping(pChannel->at0);
+ pChannel->at4 &= ~1;
+ }
+ FX_StopSound(pChannel->at0);
+}
+
+void sndStartWavDisk(const char *pzFile, int nVolume, int nChannel)
+{
+ dassert(nChannel >= -1 && nChannel < kMaxChannels);
+ SAMPLE2D *pChannel;
+ if (nChannel == -1)
+ pChannel = FindChannel();
+ else
+ pChannel = &Channel[nChannel];
+ if (pChannel->at0 > 0)
+ sndKillSound(pChannel);
+ int hFile = kopen4loadfrommod(pzFile, 0);
+ if (hFile == -1)
+ return;
+ int nLength = kfilelength(hFile);
+ char *pData = (char*)gSoundRes.Alloc(nLength);
+ if (!pData)
+ {
+ kclose(hFile);
+ return;
+ }
+ kread(hFile, pData, kfilelength(hFile));
+ kclose(hFile);
+ pChannel->at5 = (DICTNODE*)pData;
+ pChannel->at4 |= 2;
+ pChannel->at0 = FX_Play(pData, nLength, 0, -1, 0, nVolume, nVolume, nVolume, nVolume, 1.f, (intptr_t)&pChannel->at0);
+}
+
+void sndKillAllSounds(void)
+{
+ for (int i = 0; i < kMaxChannels; i++)
+ {
+ SAMPLE2D *pChannel = &Channel[i];
+ if (pChannel->at0 > 0)
+ sndKillSound(pChannel);
+ if (pChannel->at5)
+ {
+ if (pChannel->at4 & 2)
+ {
+#if 0
+ free(pChannel->at5);
+#else
+ gSoundRes.Free(pChannel->at5);
+#endif
+ pChannel->at4 &= ~2;
+ }
+ else
+ {
+ gSoundRes.Unlock(pChannel->at5);
+ }
+ pChannel->at5 = 0;
+ }
+ }
+}
+
+void sndProcess(void)
+{
+ for (int i = 0; i < kMaxChannels; i++)
+ {
+ if (Channel[i].at0 <= 0 && Channel[i].at5)
+ {
+ if (Channel[i].at4 & 2)
+ {
+ gSoundRes.Free(Channel[i].at5);
+ Channel[i].at4 &= ~2;
+ }
+ else
+ {
+ gSoundRes.Unlock(Channel[i].at5);
+ }
+ Channel[i].at5 = 0;
+ }
+ }
+}
+
+void InitSoundDevice(void)
+{
+#ifdef MIXERTYPEWIN
+ void *initdata = (void *)win_gethwnd(); // used for DirectSound
+#else
+ void *initdata = NULL;
+#endif
+ int nStatus;
+ nStatus = FX_Init(NumVoices, NumChannels, MixRate, initdata);
+ if (nStatus != 0)
+ {
+ initprintf("InitSoundDevice: %s\n", FX_ErrorString(nStatus));
+ return;
+ }
+ if (ReverseStereo == 1)
+ FX_SetReverseStereo(!FX_GetReverseStereo());
+ FX_SetVolume(FXVolume);
+ FX_SetCallBack(SoundCallback);
+}
+
+void DeinitSoundDevice(void)
+{
+ int nStatus = FX_Shutdown();
+ if (nStatus != 0)
+ ThrowError(FX_ErrorString(nStatus));
+}
+
+void InitMusicDevice(void)
+{
+ int nStatus = MUSIC_Init(MusicDevice, 0);
+ if (nStatus != 0)
+ {
+ initprintf("InitMusicDevice: %s\n", MUSIC_ErrorString(nStatus));
+ return;
+ }
+ MUSIC_SetVolume(MusicVolume);
+}
+
+void DeinitMusicDevice(void)
+{
+ FX_StopAllSounds();
+ int nStatus = MUSIC_Shutdown();
+ if (nStatus != 0)
+ ThrowError(MUSIC_ErrorString(nStatus));
+}
+
+bool sndActive = false;
+
+void sndTerm(void)
+{
+ if (!sndActive)
+ return;
+ sndActive = false;
+ sndStopSong();
+ DeinitSoundDevice();
+ DeinitMusicDevice();
+}
+extern char *pUserSoundRFF;
+void sndInit(void)
+{
+ memset(Channel, 0, sizeof(Channel));
+ pSongPtr = NULL;
+ nSongSize = 0;
+ bWaveMusic = false;
+ nWaveMusicHandle = -1;
+ InitSoundDevice();
+ InitMusicDevice();
+ //atexit(sndTerm);
+ sndActive = true;
+}
diff --git a/source/blood/src/sound.h b/source/blood/src/sound.h
new file mode 100644
index 000000000..931b29fdc
--- /dev/null
+++ b/source/blood/src/sound.h
@@ -0,0 +1,61 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "resource.h"
+struct SAMPLE2D
+{
+ int at0;
+ char at4;
+ DICTNODE *at5;
+}; // 9 bytes
+
+struct SFX
+{
+ int relVol;
+ int pitch;
+ int pitchRange;
+ int format;
+ int loopStart;
+ char rawName[9];
+};
+
+int sndGetRate(int format);
+int sndPlaySong(const char *songName, bool bLoop);
+int sndTryPlaySpecialMusic(int nMusic);
+void sndPlaySpecialMusicOrNothing(int nMusic);
+bool sndIsSongPlaying(void);
+void sndFadeSong(int nTime);
+void sndSetMusicVolume(int nVolume);
+void sndSetFXVolume(int nVolume);
+void sndStopSong(void);
+void sndStartSample(const char *pzSound, int nVolume, int nChannel = -1);
+void sndStartSample(unsigned int nSound, int nVolume, int nChannel = -1, bool bLoop = false);
+void sndStartWavID(unsigned int nSound, int nVolume, int nChannel = -1);
+void sndStartWavDisk(const char *pzFile, int nVolume, int nChannel = -1);
+void sndKillAllSounds(void);
+void sndProcess(void);
+void sndTerm(void);
+void sndInit(void);
+
+extern Resource gSoundRes;
diff --git a/source/blood/src/startgtk.game.cpp b/source/blood/src/startgtk.game.cpp
new file mode 100644
index 000000000..997e1a5ad
--- /dev/null
+++ b/source/blood/src/startgtk.game.cpp
@@ -0,0 +1,746 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010 EDuke32 developers and contributors
+
+This file is part of EDuke32.
+
+EDuke32 is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#include "build.h"
+#include "common.h"
+#include "common_game.h"
+#include "compat.h"
+#include "dynamicgtk.h"
+#include "blood.h"
+#include "gtkpixdata.h"
+
+enum
+{
+ NONE,
+ ALL,
+ POPULATE_VIDEO,
+ POPULATE_CONFIG,
+ POPULATE_GAME,
+};
+
+enum
+{
+ TAB_CONFIG,
+ TAB_GAME,
+ TAB_MESSAGES,
+};
+
+enum
+{
+ INPUT_KB,
+ INPUT_MOUSE,
+ INPUT_JOYSTICK,
+ INPUT_ALL,
+};
+
+static struct
+{
+ GtkWidget *startwin;
+ GtkWidget *hlayout;
+ GtkWidget *banner;
+ GtkWidget *vlayout;
+ GtkWidget *tabs;
+ GtkWidget *configtlayout;
+ GtkWidget *displayvlayout;
+ GtkWidget *vmode3dlabel;
+ GtkWidget *vmode3dcombo;
+ GtkWidget *fullscreencheck;
+ GtkWidget *inputdevlabel;
+ GtkWidget *inputdevcombo;
+ GtkWidget *custommodlabel;
+ GtkWidget *custommodcombo;
+ GtkWidget *emptyhlayout;
+ GtkWidget *autoloadcheck;
+ GtkWidget *alwaysshowcheck;
+ GtkWidget *configtab;
+ GtkWidget *messagesscroll;
+ GtkWidget *messagestext;
+ GtkWidget *messagestab;
+ GtkWidget *buttons;
+ GtkWidget *cancelbutton;
+ GtkWidget *cancelbuttonalign;
+ GtkWidget *cancelbuttonlayout;
+ GtkWidget *cancelbuttonicon;
+ GtkWidget *cancelbuttonlabel;
+ GtkWidget *startbutton;
+ GtkWidget *startbuttonalign;
+ GtkWidget *startbuttonlayout;
+ GtkWidget *startbuttonicon;
+ GtkWidget *startbuttonlabel;
+} stwidgets;
+
+static struct
+{
+ char *gamedir;
+ ud_setup_t shared;
+#ifdef POLYMER
+ int polymer;
+#endif
+} settings;
+
+static int32_t retval = -1, mode = TAB_MESSAGES;
+extern int32_t gtkenabled;
+static void PopulateForm(unsigned char pgs);
+
+
+// -- EVENT CALLBACKS AND CREATION STUFF --------------------------------------
+
+static void on_vmode3dcombo_changed(GtkComboBox *combobox, gpointer user_data)
+{
+ GtkTreeModel *data;
+ GtkTreeIter iter;
+ int32_t val;
+ UNREFERENCED_PARAMETER(user_data);
+
+ if (!gtk_combo_box_get_active_iter(combobox, &iter)) return;
+ if (!(data = gtk_combo_box_get_model(combobox))) return;
+ gtk_tree_model_get(data, &iter, 1, &val, -1);
+ settings.shared.xdim = validmode[val].xdim;
+ settings.shared.ydim = validmode[val].ydim;
+ settings.shared.bpp = validmode[val].bpp;
+}
+
+static void on_fullscreencheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(user_data);
+ settings.shared.fullscreen = gtk_toggle_button_get_active(togglebutton);
+ PopulateForm(POPULATE_VIDEO);
+}
+
+static void on_inputdevcombo_changed(GtkComboBox *combobox, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(user_data);
+ switch (gtk_combo_box_get_active(combobox))
+ {
+ case 0: settings.shared.usemouse = 0; settings.shared.usejoystick = 0; break;
+ case 1: settings.shared.usemouse = 1; settings.shared.usejoystick = 0; break;
+ case 2: settings.shared.usemouse = 0; settings.shared.usejoystick = 1; break;
+ case 3: settings.shared.usemouse = 1; settings.shared.usejoystick = 1; break;
+ }
+}
+
+static void on_custommodcombo_changed(GtkComboBox *combobox, gpointer user_data)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ char *value;
+ UNREFERENCED_PARAMETER(user_data);
+
+ if (gtk_combo_box_get_active_iter(combobox, &iter))
+ {
+ model = gtk_combo_box_get_model(combobox);
+ gtk_tree_model_get(model, &iter, 0,&value, -1);
+ path = gtk_tree_model_get_path(model, &iter);
+
+ if (*gtk_tree_path_get_indices(path) == NONE)
+ settings.gamedir = NULL;
+ else settings.gamedir = value;
+ }
+}
+
+static void on_autoloadcheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(user_data);
+ settings.shared.noautoload = !gtk_toggle_button_get_active(togglebutton);
+}
+
+static void on_alwaysshowcheck_toggled(GtkToggleButton *togglebutton, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(user_data);
+ settings.shared.forcesetup = gtk_toggle_button_get_active(togglebutton);
+}
+
+static void on_cancelbutton_clicked(GtkButton *button, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(button);
+ UNREFERENCED_PARAMETER(user_data);
+ if (mode == TAB_CONFIG) { retval = 0; gtk_main_quit(); }
+ else quitevent++;
+}
+
+static void on_startbutton_clicked(GtkButton *button, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(button);
+ UNREFERENCED_PARAMETER(user_data);
+ retval = 1;
+ gtk_main_quit();
+}
+
+static gboolean on_startwin_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ UNREFERENCED_PARAMETER(widget);
+ UNREFERENCED_PARAMETER(event);
+ UNREFERENCED_PARAMETER(user_data);
+ if (mode == TAB_CONFIG) { retval = 0; gtk_main_quit(); }
+ else quitevent++;
+ return TRUE; // FALSE would let the event go through. we want the game to decide when to close
+}
+
+
+// -- SUPPORT FUNCTIONS -------------------------------------------------------
+
+static GdkPixbuf *load_banner(void)
+{
+ return gdk_pixbuf_from_pixdata((GdkPixdata const *)&startbanner_pixdata, FALSE, NULL);
+}
+
+static void SetPage(int32_t n)
+{
+ if (!gtkenabled || !stwidgets.startwin) return;
+ mode = n;
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(stwidgets.tabs), n);
+
+ // each control in the config page vertical layout plus the start button should be made (in)sensitive
+ if (n == TAB_CONFIG) n = TRUE; else n = FALSE;
+ gtk_widget_set_sensitive(stwidgets.startbutton, n);
+ gtk_container_foreach(GTK_CONTAINER(stwidgets.configtlayout),
+ (GtkCallback)gtk_widget_set_sensitive,
+ (gpointer)&n);
+}
+
+static unsigned char GetModsDirNames(GtkListStore *list)
+{
+ char *homedir;
+ char pdir[BMAX_PATH];
+ unsigned char iternumb = 0;
+ CACHE1D_FIND_REC *dirs = NULL;
+ GtkTreeIter iter;
+
+ pathsearchmode = 1;
+
+ if ((homedir = Bgethomedir()))
+ {
+ Bsnprintf(pdir, sizeof(pdir), "%s/" ".blood", homedir);
+ dirs = klistpath(pdir, "*", CACHE1D_FIND_DIR);
+ for (; dirs != NULL; dirs=dirs->next)
+ {
+ if ((Bstrcmp(dirs->name, "autoload") == 0) ||
+ (Bstrcmp(dirs->name, "..") == 0) ||
+ (Bstrcmp(dirs->name, ".") == 0))
+ continue;
+ else
+ {
+ gtk_list_store_append(list, &iter);
+ gtk_list_store_set(list, &iter, 0,dirs->name, -1);
+ iternumb++;
+ }
+ }
+ }
+
+ klistfree(dirs);
+ dirs = NULL;
+
+ return iternumb;
+}
+
+static void PopulateForm(unsigned char pgs)
+{
+ if ((pgs == ALL) || (pgs == POPULATE_VIDEO))
+ {
+ int32_t mode3d, i;
+ GtkListStore *modes3d;
+ GtkTreeIter iter;
+ char buf[64];
+
+ mode3d = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, settings.shared.bpp, settings.shared.fullscreen, 1);
+ if (mode3d < 0)
+ {
+ int32_t i, cd[] = { 32, 24, 16, 15, 8, 0 };
+
+ for (i=0; cd[i];) { if (cd[i] >= settings.shared.bpp) i++; else break; }
+ for (; cd[i]; i++)
+ {
+ mode3d = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, cd[i], settings.shared.fullscreen, 1);
+ if (mode3d < 0) continue;
+ settings.shared.bpp = cd[i];
+ break;
+ }
+ }
+
+ modes3d = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(stwidgets.vmode3dcombo)));
+ gtk_list_store_clear(modes3d);
+
+ for (i=0; inext)
+ {
+ gtk_list_store_append(list, &iter);
+ gtk_list_store_set(list, &iter, 0, fg->type->name, 1, fg->filename, 2, (void const *)fg, -1);
+ if (settings.grp == fg)
+ {
+ GtkTreeSelection *sel = gtk_tree_view_get_selection(gamelist);
+ g_signal_handlers_block_by_func(sel, (gpointer)on_gamelist_selection_changed, NULL);
+ gtk_tree_selection_select_iter(sel, &iter);
+ g_signal_handlers_unblock_by_func(sel, (gpointer)on_gamelist_selection_changed, NULL);
+ }
+ }
+ }
+#endif
+}
+
+static GtkWidget *create_window(void)
+{
+ // Basic window
+ stwidgets.startwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(stwidgets.startwin), apptitle); // NOTE: use global app title
+ gtk_window_set_position(GTK_WINDOW(stwidgets.startwin), GTK_WIN_POS_CENTER);
+ gtk_window_set_resizable(GTK_WINDOW(stwidgets.startwin), FALSE);
+ gtk_window_set_type_hint(GTK_WINDOW(stwidgets.startwin), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ // Horizontal layout of banner and controls
+ stwidgets.hlayout = gtk_hbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(stwidgets.startwin), stwidgets.hlayout);
+
+ // banner
+ {
+ GdkPixbuf *pixbuf = load_banner();
+ stwidgets.banner = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref((gpointer)pixbuf);
+ }
+ gtk_box_pack_start(GTK_BOX(stwidgets.hlayout), stwidgets.banner, FALSE, FALSE, 0);
+ gtk_misc_set_alignment(GTK_MISC(stwidgets.banner), 0.5, 0);
+
+ // Vertical layout of tab control and start+cancel buttons
+ stwidgets.vlayout = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(stwidgets.hlayout), stwidgets.vlayout, TRUE, TRUE, 0);
+
+ // Tab control
+ stwidgets.tabs = gtk_notebook_new();
+ gtk_box_pack_start(GTK_BOX(stwidgets.vlayout), stwidgets.tabs, TRUE, TRUE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(stwidgets.tabs), 4);
+
+ // layout table of config page
+ stwidgets.configtlayout = gtk_table_new(6, 3, FALSE);
+ gtk_container_add(GTK_CONTAINER(stwidgets.tabs), stwidgets.configtlayout);
+
+ // 3D video mode LabelText
+ stwidgets.vmode3dlabel = gtk_label_new_with_mnemonic("_Video mode:");
+ gtk_misc_set_alignment(GTK_MISC(stwidgets.vmode3dlabel), 0.3, 0);
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.vmode3dlabel, 0,1, 0,1, GTK_FILL, (GtkAttachOptions)0, 4, 7);
+
+ // 3D video mode combo
+ {
+ GtkListStore *list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
+ GtkCellRenderer *cell;
+
+ stwidgets.vmode3dcombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
+ g_object_unref(G_OBJECT(list));
+
+ cell = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(stwidgets.vmode3dcombo), cell, FALSE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(stwidgets.vmode3dcombo), cell, "text", 0, NULL);
+ }
+
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.vmode3dcombo, 1,2, 0,1,
+ (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 4, 0);
+
+ // Fullscreen checkbox
+ stwidgets.displayvlayout = gtk_vbox_new(TRUE, 0);
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.displayvlayout, 2,3, 0,1, GTK_FILL, (GtkAttachOptions)0, 4, 0);
+
+ stwidgets.fullscreencheck = gtk_check_button_new_with_mnemonic("_Fullscreen");
+ gtk_box_pack_start(GTK_BOX(stwidgets.displayvlayout), stwidgets.fullscreencheck, FALSE, FALSE, 0);
+
+ // Input devices LabelText
+ stwidgets.inputdevlabel = gtk_label_new_with_mnemonic("_Input devices:");
+ gtk_misc_set_alignment(GTK_MISC(stwidgets.inputdevlabel), 0.3, 0);
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.inputdevlabel, 0,1, 1,2, GTK_FILL, (GtkAttachOptions)0, 4, 0);
+
+ // Input devices combo
+ {
+ GtkListStore *list = gtk_list_store_new(1, G_TYPE_STRING);
+ GtkCellRenderer *cell;
+
+ stwidgets.inputdevcombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
+ g_object_unref(G_OBJECT(list));
+
+ cell = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(stwidgets.inputdevcombo), cell, FALSE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(stwidgets.inputdevcombo), cell, "text", 0, NULL);
+ }
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.inputdevcombo, 1,2, 1,2,
+ (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 4, 0);
+
+ // Custom mod LabelText
+ stwidgets.custommodlabel = gtk_label_new_with_mnemonic("Custom _game:");
+ gtk_misc_set_alignment(GTK_MISC(stwidgets.custommodlabel), 0.3, 0);
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.custommodlabel, 0,1, 2,3, GTK_FILL, (GtkAttachOptions)0, 4, 7);
+
+ // Custom mod combo
+ {
+ GtkListStore *list = gtk_list_store_new(1, G_TYPE_STRING);
+ GtkCellRenderer *cell;
+
+ stwidgets.custommodcombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(list));
+ g_object_unref(G_OBJECT(list));
+
+ cell = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(stwidgets.custommodcombo), cell, FALSE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(stwidgets.custommodcombo), cell, "text", 0, NULL);
+ }
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.custommodcombo, 1,2, 2,3,
+ (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)0, 4, 7);
+
+ // Empty horizontal layout
+ stwidgets.emptyhlayout = gtk_hbox_new(TRUE, 0);
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.emptyhlayout, 0,3, 3,4, (GtkAttachOptions)0,
+ (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 4, 0);
+
+ // Autoload checkbox
+ stwidgets.autoloadcheck = gtk_check_button_new_with_mnemonic("_Enable \"autoload\" folder");
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.autoloadcheck, 0,3, 4,5, GTK_FILL, (GtkAttachOptions)0, 2, 2);
+
+ // Always show config checkbox
+ stwidgets.alwaysshowcheck = gtk_check_button_new_with_mnemonic("_Always show this window at startup");
+ gtk_table_attach(GTK_TABLE(stwidgets.configtlayout), stwidgets.alwaysshowcheck, 0,3, 5,6, GTK_FILL, (GtkAttachOptions)0, 2, 2);
+
+ // Configuration tab
+ stwidgets.configtab = gtk_label_new("Configuration");
+ gtk_notebook_set_tab_label(GTK_NOTEBOOK(stwidgets.tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(stwidgets.tabs), 0), stwidgets.configtab);
+
+ // Messages scrollable area
+ stwidgets.messagesscroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_container_add(GTK_CONTAINER(stwidgets.tabs), stwidgets.messagesscroll);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(stwidgets.messagesscroll), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+
+ // Messages text area
+ stwidgets.messagestext = gtk_text_view_new();
+ gtk_container_add(GTK_CONTAINER(stwidgets.messagesscroll), stwidgets.messagestext);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(stwidgets.messagestext), FALSE);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(stwidgets.messagestext), GTK_WRAP_WORD);
+ gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(stwidgets.messagestext), FALSE);
+ gtk_text_view_set_left_margin(GTK_TEXT_VIEW(stwidgets.messagestext), 2);
+ gtk_text_view_set_right_margin(GTK_TEXT_VIEW(stwidgets.messagestext), 2);
+
+ // Messages tab
+ stwidgets.messagestab = gtk_label_new("Messages");
+ gtk_notebook_set_tab_label(GTK_NOTEBOOK(stwidgets.tabs), gtk_notebook_get_nth_page(GTK_NOTEBOOK(stwidgets.tabs), 1), stwidgets.messagestab);
+
+ // Dialogue box buttons layout
+ stwidgets.buttons = gtk_hbutton_box_new();
+ gtk_box_pack_start(GTK_BOX(stwidgets.vlayout), stwidgets.buttons, FALSE, TRUE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(stwidgets.buttons), 3);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(stwidgets.buttons), GTK_BUTTONBOX_END);
+
+ // Cancel button
+ stwidgets.cancelbutton = gtk_button_new();
+ gtk_container_add(GTK_CONTAINER(stwidgets.buttons), stwidgets.cancelbutton);
+ GTK_WIDGET_SET_FLAGS(stwidgets.cancelbutton, GTK_CAN_DEFAULT);
+
+ stwidgets.cancelbuttonalign = gtk_alignment_new(0.5, 0.5, 0, 0);
+ gtk_container_add(GTK_CONTAINER(stwidgets.cancelbutton), stwidgets.cancelbuttonalign);
+
+ stwidgets.cancelbuttonlayout = gtk_hbox_new(FALSE, 2);
+ gtk_container_add(GTK_CONTAINER(stwidgets.cancelbuttonalign), stwidgets.cancelbuttonlayout);
+
+ stwidgets.cancelbuttonicon = gtk_image_new_from_stock("gtk-cancel", GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start(GTK_BOX(stwidgets.cancelbuttonlayout), stwidgets.cancelbuttonicon, FALSE, FALSE, 0);
+
+ stwidgets.cancelbuttonlabel = gtk_label_new_with_mnemonic("_Cancel");
+ gtk_box_pack_start(GTK_BOX(stwidgets.cancelbuttonlayout), stwidgets.cancelbuttonlabel, FALSE, FALSE, 0);
+
+ // Start button
+ stwidgets.startbutton = gtk_button_new();
+ gtk_container_add(GTK_CONTAINER(stwidgets.buttons), stwidgets.startbutton);
+ GTK_WIDGET_SET_FLAGS(stwidgets.startbutton, GTK_CAN_DEFAULT);
+
+ gtk_window_set_default(GTK_WINDOW(stwidgets.startwin), stwidgets.startbutton);
+
+ stwidgets.startbuttonalign = gtk_alignment_new(0.5, 0.5, 0, 0);
+ gtk_container_add(GTK_CONTAINER(stwidgets.startbutton), stwidgets.startbuttonalign);
+
+ stwidgets.startbuttonlayout = gtk_hbox_new(FALSE, 2);
+ gtk_container_add(GTK_CONTAINER(stwidgets.startbuttonalign), stwidgets.startbuttonlayout);
+
+ stwidgets.startbuttonicon = gtk_image_new_from_stock("gtk-execute", GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start(GTK_BOX(stwidgets.startbuttonlayout), stwidgets.startbuttonicon, FALSE, FALSE, 0);
+
+ stwidgets.startbuttonlabel = gtk_label_new_with_mnemonic("_Start");
+ gtk_box_pack_start(GTK_BOX(stwidgets.startbuttonlayout), stwidgets.startbuttonlabel, FALSE, FALSE, 0);
+
+ // Wire up the signals
+ g_signal_connect((gpointer) stwidgets.startwin, "delete_event",
+ G_CALLBACK(on_startwin_delete_event),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.vmode3dcombo, "changed",
+ G_CALLBACK(on_vmode3dcombo_changed),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.fullscreencheck, "toggled",
+ G_CALLBACK(on_fullscreencheck_toggled),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.inputdevcombo, "changed",
+ G_CALLBACK(on_inputdevcombo_changed),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.custommodcombo, "changed",
+ G_CALLBACK(on_custommodcombo_changed),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.autoloadcheck, "toggled",
+ G_CALLBACK(on_autoloadcheck_toggled),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.alwaysshowcheck, "toggled",
+ G_CALLBACK(on_alwaysshowcheck_toggled),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.cancelbutton, "clicked",
+ G_CALLBACK(on_cancelbutton_clicked),
+ NULL);
+ g_signal_connect((gpointer) stwidgets.startbutton, "clicked",
+ G_CALLBACK(on_startbutton_clicked),
+ NULL);
+
+ // Associate labels with their controls
+ gtk_label_set_mnemonic_widget(GTK_LABEL(stwidgets.vmode3dlabel), stwidgets.vmode3dcombo);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(stwidgets.inputdevlabel), stwidgets.inputdevcombo);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(stwidgets.custommodlabel), stwidgets.custommodcombo);
+
+ return stwidgets.startwin;
+}
+
+
+// -- BUILD ENTRY POINTS ------------------------------------------------------
+
+int32_t startwin_open(void)
+{
+ if (!gtkenabled) return 0;
+ if (stwidgets.startwin) return 1;
+
+ stwidgets.startwin = create_window();
+ if (stwidgets.startwin)
+ {
+ SetPage(TAB_MESSAGES);
+ gtk_widget_show_all(stwidgets.startwin);
+ gtk_main_iteration_do(FALSE);
+ return 0;
+ }
+ return -1;
+}
+
+int32_t startwin_close(void)
+{
+ if (!gtkenabled) return 0;
+ if (!stwidgets.startwin) return 1;
+ gtk_widget_destroy(stwidgets.startwin);
+ stwidgets.startwin = NULL;
+ return 0;
+}
+
+int32_t startwin_puts(const char *str)
+{
+ GtkWidget *textview;
+ GtkTextBuffer *textbuffer;
+ GtkTextIter enditer;
+ GtkTextMark *mark;
+ const char *aptr, *bptr;
+
+ if (!gtkenabled || !str) return 0;
+ if (!stwidgets.startwin) return 1;
+ if (!(textview = stwidgets.messagestext)) return -1;
+ textbuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
+
+ gtk_text_buffer_get_end_iter(textbuffer, &enditer);
+ for (aptr = bptr = str; *aptr != 0;)
+ {
+ switch (*bptr)
+ {
+ case '\b':
+ if (bptr > aptr)
+ gtk_text_buffer_insert(textbuffer, &enditer, (const gchar *)aptr, (gint)(bptr-aptr)-1);
+#if GTK_CHECK_VERSION(2,6,0)
+ gtk_text_buffer_backspace(textbuffer, &enditer, FALSE, TRUE);
+#else
+ {
+ GtkTextIter iter2 = enditer;
+ gtk_text_iter_backward_cursor_position(&iter2);
+ //FIXME: this seems be deleting one too many chars somewhere!
+ if (!gtk_text_iter_equal(&iter2, &enditer))
+ gtk_text_buffer_delete_interactive(textbuffer, &iter2, &enditer, TRUE);
+ }
+#endif
+ aptr = ++bptr;
+ break;
+ case 0:
+ if (bptr > aptr)
+ gtk_text_buffer_insert(textbuffer, &enditer, (const gchar *)aptr, (gint)(bptr-aptr));
+ aptr = bptr;
+ break;
+ case '\r': // FIXME
+ default:
+ bptr++;
+ break;
+ }
+ }
+
+ mark = gtk_text_buffer_create_mark(textbuffer, NULL, &enditer, 1);
+ gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), mark, 0.0, FALSE, 0.0, 1.0);
+ gtk_text_buffer_delete_mark(textbuffer, mark);
+
+ return 0;
+}
+
+int32_t startwin_settitle(const char *title)
+{
+ if (!gtkenabled) return 0;
+ if (!stwidgets.startwin) return 1;
+ gtk_window_set_title(GTK_WINDOW(stwidgets.startwin), title);
+ return 0;
+}
+
+int32_t startwin_idle(void *s)
+{
+ UNREFERENCED_PARAMETER(s);
+ if (!gtkenabled) return 0;
+ //if (!stwidgets.startwin) return 1;
+ gtk_main_iteration_do(FALSE);
+ return 0;
+}
+
+int32_t startwin_run(void)
+{
+ if (!gtkenabled) return 1;
+ if (!stwidgets.startwin) return 1;
+
+ SetPage(TAB_CONFIG);
+
+ settings.shared = gSetup;
+ settings.gamedir = g_modDir;
+ //settings.grp = g_selectedGrp;
+#ifdef POLYMER
+ settings.polymer = 0;
+#endif
+ PopulateForm(ALL);
+
+ gtk_main();
+
+ SetPage(TAB_MESSAGES);
+ if (retval) // launch the game with these parameters
+ {
+ gSetup = settings.shared;
+#ifdef POLYMER
+ glrendmode = (settings.polymer) ? REND_POLYMER : REND_POLYMOST;
+#endif
+ //g_selectedGrp = settings.grp;
+
+ Bstrcpy(g_modDir, (gNoSetup == 0 && settings.gamedir != NULL) ? settings.gamedir : "/");
+ }
+
+ return retval;
+}
diff --git a/source/blood/src/startosx.game.mm b/source/blood/src/startosx.game.mm
new file mode 100644
index 000000000..25e0d29cd
--- /dev/null
+++ b/source/blood/src/startosx.game.mm
@@ -0,0 +1,856 @@
+
+#define Rect CocoaRect
+
+#import
+
+#undef Rect
+
+#include "blood.h"
+#include "common.h"
+#include "common_game.h"
+#include "build.h"
+#include "compat.h"
+#include "baselayer.h"
+
+#ifndef MAC_OS_X_VERSION_10_5
+# define NSImageScaleNone NSScaleNone
+#endif
+
+#ifndef MAC_OS_X_VERSION_10_12
+# define NSEventModifierFlagOption NSAlternateKeyMask
+# define NSEventModifierFlagCommand NSCommandKeyMask
+# define NSEventMaskAny NSAnyEventMask
+# define NSWindowStyleMaskTitled NSTitledWindowMask
+# define NSWindowStyleMaskClosable NSClosableWindowMask
+# define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask
+# define NSWindowStyleMaskResizable NSResizableWindowMask
+# define NSAlertStyleInformational NSInformationalAlertStyle
+# define NSControlSizeSmall NSSmallControlSize
+#endif
+
+@interface GameEntry : NSObject
+{
+ NSString *namestring;
+ NSString *inifilestring;
+ INICHAIN const *fg;
+}
+
+- (id)initWithINICHAIN:(INICHAIN const *)inichain;
+- (void)dealloc;
+
+- (NSString *)name;
+- (NSString *)inifile;
+- (INICHAIN const *)entryptr;
+
+@end
+
+@implementation GameEntry
+
+- (id)initWithINICHAIN:(INICHAIN const *)inichain
+{
+ self = [super init];
+
+ if (self)
+ {
+ fg = inichain;
+
+ char const *const name = nullptr == fg->pDescription ? fg->zName : fg->pDescription->pzName;
+
+ namestring = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
+ [namestring retain];
+
+ inifilestring = [NSString stringWithCString:fg->zName encoding:NSUTF8StringEncoding];
+ [inifilestring retain];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [namestring release];
+ [inifilestring release];
+ [super dealloc];
+}
+
+- (NSString *)name
+{
+ return namestring;
+}
+
+- (NSString *)inifile
+{
+ return inifilestring;
+}
+
+- (INICHAIN const *)entryptr
+{
+ return fg;
+}
+
+@end
+
+@interface GameListSource : NSObject
+{
+ NSMutableArray *list;
+}
+
+- (id)init;
+- (void)dealloc;
+
+- (GameEntry *)entryAtIndex:(int)index;
+- (int)findIndexForINI:(NSString*)inifile;
+
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex;
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView;
+
+@end
+
+@implementation GameListSource
+
+- (id)init
+{
+ self = [super init];
+
+ if (self)
+ {
+ list = [[NSMutableArray alloc] init];
+
+ for (auto fg = pINIChain; nullptr != fg; fg = fg->pNext)
+ {
+ [list addObject:[[GameEntry alloc] initWithINICHAIN:fg]];
+ }
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [list release];
+ [super dealloc];
+}
+
+- (GameEntry *)entryAtIndex:(int)index
+{
+ return [list objectAtIndex:index];
+}
+
+- (int)findIndexForINI:(NSString*)inifile
+{
+ for (NSUInteger i = 0, count = [list count]; i < count; ++i)
+ {
+ if ([[[list objectAtIndex:i] inifile] isEqual:inifile])
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
+{
+ UNREFERENCED_PARAMETER(aTableView);
+ NSParameterAssert((NSUInteger)rowIndex < [list count]);
+
+ switch ([[aTableColumn identifier] intValue])
+ {
+ case 0: // name column
+ return [[list objectAtIndex:rowIndex] name];
+ case 1: // ini file column
+ return [[list objectAtIndex:rowIndex] inifile];
+ default:
+ return nil;
+ }
+}
+
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+ UNREFERENCED_PARAMETER(aTableView);
+
+ return [list count];
+}
+
+@end
+
+static NSRect NSRectChangeXY(NSRect const rect, CGFloat const x, CGFloat const y)
+{
+ return NSMakeRect(x, y, rect.size.width, rect.size.height);
+}
+static NSRect NSSizeAddXY(NSSize const size, CGFloat const x, CGFloat const y)
+{
+ return NSMakeRect(x, y, size.width, size.height);
+}
+#if 0
+static CGFloat NSRightEdge(NSRect rect)
+{
+ return rect.origin.x + rect.size.width;
+}
+#endif
+static CGFloat NSTopEdge(NSRect rect)
+{
+ return rect.origin.y + rect.size.height;
+}
+
+static void setFontToSmall(id control)
+{
+ [control setFont:[NSFont fontWithDescriptor:[[control font] fontDescriptor] size:[NSFont smallSystemFontSize]]];
+}
+
+static void setControlToSmall(id control)
+{
+#ifdef MAC_OS_X_VERSION_10_12
+ [control setControlSize:NSControlSizeSmall];
+#else
+ [control setControlSize:NSControlSizeSmall];
+#endif
+}
+
+static NSTextField * makeLabel(NSString * labelText)
+{
+ NSTextField *textField = [[NSTextField alloc] init];
+ setFontToSmall(textField);
+ setControlToSmall([textField cell]);
+ [textField setStringValue:labelText];
+ [textField setBezeled:NO];
+ [textField setDrawsBackground:NO];
+ [textField setEditable:NO];
+ [textField setSelectable:NO];
+ [textField sizeToFit];
+ return textField;
+}
+
+static NSButton * makeCheckbox(NSString * labelText)
+{
+ NSButton *checkbox = [[NSButton alloc] init];
+ setFontToSmall(checkbox);
+ setControlToSmall([checkbox cell]);
+ [checkbox setTitle:labelText];
+ [checkbox setButtonType:NSSwitchButton];
+ [checkbox sizeToFit];
+ return checkbox;
+}
+
+static NSPopUpButton * makeComboBox(void)
+{
+ NSPopUpButton *comboBox = [[NSPopUpButton alloc] init];
+ [comboBox setPullsDown:NO];
+ setFontToSmall(comboBox);
+ setControlToSmall([comboBox cell]);
+ [comboBox setBezelStyle:NSRoundedBezelStyle];
+ [comboBox setPreferredEdge:NSMaxYEdge];
+ [[comboBox cell] setArrowPosition:NSPopUpArrowAtCenter];
+ [comboBox sizeToFit];
+ return comboBox;
+}
+
+static id nsapp;
+
+/* setAppleMenu disappeared from the headers in 10.4 */
+@interface NSApplication(NSAppleMenu)
+- (void)setAppleMenu:(NSMenu *)menu;
+@end
+
+static NSString * GetApplicationName(void)
+{
+ NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
+ if (!appName)
+ appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+ if (![appName length])
+ appName = [[NSProcessInfo processInfo] processName];
+
+ return appName;
+}
+
+static void CreateApplicationMenus(void)
+{
+ NSString *appName;
+ NSString *title;
+ NSMenu *rootMenu;
+ NSMenu *serviceMenu;
+ NSMenuItem *menuItem;
+
+ NSMenu *mainMenu = [[NSMenu alloc] init];
+
+ /* Create the application menu */
+ appName = GetApplicationName();
+ rootMenu = [[NSMenu alloc] init];
+
+ /* Put menu into the menubar */
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+ [menuItem setSubmenu:rootMenu];
+ [mainMenu addItem:menuItem];
+ [menuItem release];
+
+ /* Add menu items */
+ title = [@"About " stringByAppendingString:appName];
+ [rootMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
+
+ [rootMenu addItem:[NSMenuItem separatorItem]];
+
+ serviceMenu = [[NSMenu alloc] init];
+ menuItem = (NSMenuItem *)[rootMenu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
+ [menuItem setSubmenu:serviceMenu];
+
+ [nsapp setServicesMenu:serviceMenu];
+ [serviceMenu release];
+
+ [rootMenu addItem:[NSMenuItem separatorItem]];
+
+ title = [@"Hide " stringByAppendingString:appName];
+ [rootMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+
+ menuItem = (NSMenuItem *)[rootMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+ [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
+
+ [rootMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+ [rootMenu addItem:[NSMenuItem separatorItem]];
+
+ title = [@"Quit " stringByAppendingString:appName];
+ [rootMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+ /* Create the main menu bar */
+ [nsapp setMainMenu:mainMenu];
+ [mainMenu release]; /* we're done with it, let NSApp own it. */
+
+ /* Tell the application object that this is now the application menu */
+ [nsapp setAppleMenu:rootMenu];
+ [rootMenu release];
+}
+
+static int retval = -1;
+
+static struct
+{
+ INICHAIN const * ini;
+ char *gamedir;
+ ud_setup_t shared;
+ int polymer;
+}
+settings;
+
+@interface StartupWindow : NSWindow
+{
+ NSMutableArray *modeslist3d;
+ GameListSource *gamelistsrc;
+
+ NSButton *alwaysShowButton;
+ NSButton *fullscreenButton;
+ NSTextView *messagesView;
+ NSTabView *tabView;
+ NSTabViewItem *tabViewItemSetup;
+ NSTabViewItem *tabViewItemMessageLog;
+ NSPopUpButton *videoMode3DPUButton;
+ NSScrollView *gameList;
+
+ NSButton *cancelButton;
+ NSButton *startButton;
+}
+
+- (StartupWindow *)init;
+
+- (void)dealloc;
+- (void)populateVideoModes:(BOOL)firstTime;
+
+- (void)fullscreenClicked:(id)sender;
+
+- (void)cancel:(id)sender;
+- (void)start:(id)sender;
+
+- (void)setupRunMode;
+- (void)setupMessagesMode;
+
+- (void)putsMessage:(NSString *)str;
+
+@end
+
+@implementation StartupWindow : NSWindow
+
+- (StartupWindow *)init
+{
+ NSUInteger const style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
+ NSRect const windowFrame = NSMakeRect(0, 0, 480, 280);
+ self = [super initWithContentRect:windowFrame styleMask:style backing:NSBackingStoreBuffered defer:NO];
+
+ if (self)
+ {
+ // window properties
+ [self setDelegate:self];
+ [self setReleasedWhenClosed:NO];
+#if defined MAC_OS_X_VERSION_10_3 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
+ [self setContentMinSize:[[self contentView] frame].size];
+#else
+ [self setMinSize:[NSWindow frameRectForContentRect:[[self contentView] frame] styleMask:[self styleMask]].size];
+#endif
+
+
+ // image on the left
+ NSRect const imageFrame = NSMakeRect(0, 0, 100, 280);
+ NSImageView * imageView = [[NSImageView alloc] initWithFrame:imageFrame];
+ [imageView setImageScaling:NSImageScaleNone];
+ [imageView setImage:[NSImage imageNamed:@"game"]];
+ [[self contentView] addSubview:imageView];
+ [imageView setAutoresizingMask:NSViewMaxXMargin | NSViewHeightSizable];
+
+
+ // buttons
+ CGFloat const buttonWidth = 80;
+ CGFloat const buttonHeight = 32;
+
+ NSRect const startButtonFrame = NSMakeRect(windowFrame.size.width - buttonWidth, 0, buttonWidth, buttonHeight);
+ startButton = [[NSButton alloc] initWithFrame:startButtonFrame];
+ [[self contentView] addSubview:startButton];
+ [startButton setTitle:@"Start"];
+ [startButton setTarget:self];
+ [startButton setAction:@selector(start:)];
+ [startButton setBezelStyle:NSRoundedBezelStyle];
+ [startButton setKeyEquivalent:@"\r"];
+ [startButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin];
+
+ NSRect const cancelButtonFrame = NSMakeRect(startButtonFrame.origin.x - buttonWidth, 0, buttonWidth, buttonHeight);
+ cancelButton = [[NSButton alloc] initWithFrame:cancelButtonFrame];
+ [[self contentView] addSubview:cancelButton];
+ [cancelButton setTitle:@"Cancel"];
+ [cancelButton setTarget:self];
+ [cancelButton setAction:@selector(cancel:)];
+ [cancelButton setBezelStyle:NSRoundedBezelStyle];
+ [cancelButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin];
+
+
+ // tab frame
+ NSRect const tabViewFrame = NSMakeRect(imageFrame.size.width, buttonHeight, windowFrame.size.width - imageFrame.size.width, windowFrame.size.height - buttonHeight - 5);
+ tabView = [[NSTabView alloc] initWithFrame:tabViewFrame];
+ [[self contentView] addSubview:tabView];
+ setFontToSmall(tabView);
+ setControlToSmall(tabView);
+ [tabView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+
+ // setup tab
+
+ tabViewItemSetup = [[NSTabViewItem alloc] init];
+ [tabView addTabViewItem:tabViewItemSetup];
+ [tabViewItemSetup setLabel:@"Setup"];
+ NSRect const tabViewItemSetupFrame = [[tabViewItemSetup view] frame];
+
+
+ // always show checkbox
+ alwaysShowButton = makeCheckbox(@"Always show this window at startup");
+ [[tabViewItemSetup view] addSubview:alwaysShowButton];
+ NSSize const alwaysShowButtonSize = [alwaysShowButton frame].size;
+ NSRect const alwaysShowButtonFrame = NSSizeAddXY(alwaysShowButtonSize, tabViewItemSetupFrame.size.width - alwaysShowButtonSize.width, 0);
+ [alwaysShowButton setFrame:alwaysShowButtonFrame];
+ [alwaysShowButton setAutoresizingMask:NSViewMinXMargin | NSViewMaxYMargin];
+
+
+ // video mode selectors and labels
+ NSTextField * labelVideoMode = makeLabel(@"Video mode:");
+ [[tabViewItemSetup view] addSubview:labelVideoMode];
+ NSSize const labelVideoModeSize = [labelVideoMode frame].size;
+ [labelVideoMode setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
+
+ fullscreenButton = makeCheckbox(@"Fullscreen");
+ [[tabViewItemSetup view] addSubview:fullscreenButton];
+ NSSize const fullscreenButtonSize = [fullscreenButton frame].size;
+ [fullscreenButton setAction:@selector(fullscreenClicked:)];
+ [fullscreenButton setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
+
+ videoMode3DPUButton = makeComboBox();
+ [[tabViewItemSetup view] addSubview:videoMode3DPUButton];
+ NSSize const videoMode3DPUButtonSize = [videoMode3DPUButton frame].size;
+ CGFloat const videoMode3DButtonX = labelVideoModeSize.width; // NSRightEdge(labelVideoModeFrame);
+ NSRect const videoMode3DPUButtonFrame = NSMakeRect(videoMode3DButtonX, tabViewItemSetupFrame.size.height - videoMode3DPUButtonSize.height, tabViewItemSetupFrame.size.width - videoMode3DButtonX - fullscreenButtonSize.width, videoMode3DPUButtonSize.height);
+ [videoMode3DPUButton setFrame:videoMode3DPUButtonFrame];
+ [videoMode3DPUButton setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
+
+ NSRect const labelVideoModeFrame = NSSizeAddXY(labelVideoModeSize, 0, videoMode3DPUButtonFrame.origin.y + rintf((videoMode3DPUButtonSize.height - labelVideoModeSize.height) * 0.5f) + 1);
+ [labelVideoMode setFrame:labelVideoModeFrame];
+
+ NSRect const fullscreenButtonFrame = NSSizeAddXY(fullscreenButtonSize, tabViewItemSetupFrame.size.width - fullscreenButtonSize.width, videoMode3DPUButtonFrame.origin.y + rintf((videoMode3DPUButtonSize.height - fullscreenButtonSize.height) * 0.5f) + 1);
+ [fullscreenButton setFrame:fullscreenButtonFrame];
+
+
+ // game selector and label
+ NSTextField * labelGame = makeLabel(@"Game:");
+ [[tabViewItemSetup view] addSubview:labelGame];
+ NSSize const labelGameSize = [labelGame frame].size;
+ NSRect const labelGameFrame = NSSizeAddXY(labelGameSize, 0, videoMode3DPUButtonFrame.origin.y - labelGameSize.height);
+ [labelGame setFrame:labelGameFrame];
+ [labelGame setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
+
+ CGFloat const gameListVerticalPadding = 3;
+ CGFloat const gameListY = NSTopEdge(alwaysShowButtonFrame) + gameListVerticalPadding;
+ NSRect const gameListFrame = NSMakeRect(0, gameListY, tabViewItemSetupFrame.size.width, labelGameFrame.origin.y - gameListY - gameListVerticalPadding);
+ gameList = [[NSScrollView alloc] initWithFrame:gameListFrame];
+ [[tabViewItemSetup view] addSubview:gameList];
+ [gameList setBorderType:NSBezelBorder];
+ [gameList setHasVerticalScroller:YES];
+ [gameList setHasHorizontalScroller:NO];
+ setControlToSmall([[gameList verticalScroller] cell]);
+ NSSize const gameListContentSize = [gameList contentSize];
+ [gameList setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+ NSTableView * gameListTable = [[NSTableView alloc] initWithFrame:NSMakeRect(0, 0, gameListContentSize.width, gameListContentSize.height)];
+ [gameList setDocumentView:gameListTable];
+
+ NSTableColumn * nameColumn = [[NSTableColumn alloc] initWithIdentifier:@"0"];
+ [gameListTable addTableColumn:nameColumn];
+ NSTableColumn * fileColumn = [[NSTableColumn alloc] initWithIdentifier:@"1"];
+ [gameListTable addTableColumn:fileColumn];
+ [nameColumn setEditable:NO];
+ [[nameColumn headerCell] setStringValue:@"Name"];
+ [nameColumn setWidth:gameListContentSize.width * (2.f/3.f)];
+ [fileColumn setEditable:NO];
+ [[fileColumn headerCell] setStringValue:@"File"];
+ [gameListTable sizeLastColumnToFit];
+ [gameListTable setAutoresizingMask:NSViewWidthSizable];
+
+
+ // message log tab
+
+ tabViewItemMessageLog = [[NSTabViewItem alloc] init];
+ [tabView addTabViewItem:tabViewItemMessageLog];
+ [tabViewItemMessageLog setLabel:@"Message Log"];
+ NSRect const tabViewItemMessageLogFrame = [[tabViewItemMessageLog view] frame];
+
+
+ // message log
+ NSScrollView * messagesScrollView = [[NSScrollView alloc] initWithFrame:NSRectChangeXY(tabViewItemMessageLogFrame, 0, 0)];
+ [[tabViewItemMessageLog view] addSubview:messagesScrollView];
+ [messagesScrollView setBorderType:NSBezelBorder];
+ [messagesScrollView setHasVerticalScroller:YES];
+ [messagesScrollView setHasHorizontalScroller:NO];
+ setControlToSmall([[messagesScrollView verticalScroller] cell]);
+ NSSize const messagesScrollViewContentSize = [messagesScrollView contentSize];
+ [messagesScrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+ messagesView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, messagesScrollViewContentSize.width, messagesScrollViewContentSize.height)];
+ [messagesScrollView setDocumentView:messagesView];
+ [messagesView setEditable:NO];
+ [messagesView setRichText:NO];
+ setFontToSmall(messagesView);
+ [messagesView setMinSize:NSMakeSize(0.0, messagesScrollViewContentSize.height)];
+ [messagesView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+ [messagesView setVerticallyResizable:YES];
+ [messagesView setHorizontallyResizable:NO];
+ [messagesView setAutoresizingMask:NSViewWidthSizable];
+
+ [[messagesView textContainer] setContainerSize:NSMakeSize(messagesScrollViewContentSize.width, FLT_MAX)];
+ [[messagesView textContainer] setWidthTracksTextView:YES];
+ }
+
+ return self;
+}
+
+- (BOOL)canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (BOOL)canBecomeMainWindow
+{
+ return YES;
+}
+
+- (BOOL) windowShouldClose:(id)sender
+{
+ UNREFERENCED_PARAMETER(sender);
+
+ retval = 0;
+
+ return YES;
+}
+
+- (void)dealloc
+{
+ [gamelistsrc release];
+ [modeslist3d release];
+ [super dealloc];
+}
+
+- (void)populateVideoModes:(BOOL)firstTime
+{
+ int i, mode3d, fullscreen = ([fullscreenButton state] == NSOnState);
+ int idx3d = -1;
+ int xdim = 0, ydim = 0, bpp = 0;
+
+ if (firstTime) {
+ xdim = settings.shared.xdim;
+ ydim = settings.shared.ydim;
+ bpp = settings.shared.bpp;
+ } else {
+ mode3d = [[modeslist3d objectAtIndex:[videoMode3DPUButton indexOfSelectedItem]] intValue];
+ if (mode3d >= 0) {
+ xdim = validmode[mode3d].xdim;
+ ydim = validmode[mode3d].ydim;
+ bpp = validmode[mode3d].bpp;
+ }
+
+ }
+ mode3d = videoCheckMode(&xdim, &ydim, bpp, fullscreen, 1);
+ if (mode3d < 0) {
+ int i, cd[] = { 32, 24, 16, 15, 8, 0 };
+ for (i=0; cd[i]; ) { if (cd[i] >= bpp) i++; else break; }
+ for ( ; cd[i]; i++) {
+ mode3d = videoCheckMode(&xdim, &ydim, cd[i], fullscreen, 1);
+ if (mode3d < 0) continue;
+ break;
+ }
+ }
+
+ [modeslist3d release];
+ [videoMode3DPUButton removeAllItems];
+
+ modeslist3d = [[NSMutableArray alloc] init];
+
+ for (i = 0; i < validmodecnt; i++) {
+ if (fullscreen == validmode[i].fs) {
+ if (i == mode3d) idx3d = [modeslist3d count];
+ [modeslist3d addObject:[NSNumber numberWithInt:i]];
+ [videoMode3DPUButton addItemWithTitle:[NSString stringWithFormat:@"%d %C %d %d-bpp",
+ validmode[i].xdim, 0xd7, validmode[i].ydim, validmode[i].bpp]];
+ }
+ }
+
+ if (idx3d >= 0) [videoMode3DPUButton selectItemAtIndex:idx3d];
+}
+
+- (void)fullscreenClicked:(id)sender
+{
+ UNREFERENCED_PARAMETER(sender);
+
+ [self populateVideoModes:NO];
+}
+
+- (void)cancel:(id)sender
+{
+ UNREFERENCED_PARAMETER(sender);
+
+ retval = 0;
+}
+
+- (void)start:(id)sender
+{
+ UNREFERENCED_PARAMETER(sender);
+
+ int mode = [[modeslist3d objectAtIndex:[videoMode3DPUButton indexOfSelectedItem]] intValue];
+ if (mode >= 0) {
+ settings.shared.xdim = validmode[mode].xdim;
+ settings.shared.ydim = validmode[mode].ydim;
+ settings.shared.bpp = validmode[mode].bpp;
+ settings.shared.fullscreen = validmode[mode].fs;
+ }
+
+ int row = [[gameList documentView] selectedRow];
+ if (row >= 0)
+ {
+ settings.ini = [[gamelistsrc entryAtIndex:row] entryptr];
+ }
+
+ settings.shared.forcesetup = [alwaysShowButton state] == NSOnState;
+
+ retval = 1;
+}
+
+- (void)setupRunMode
+{
+ videoGetModes();
+
+ [fullscreenButton setState: (settings.shared.fullscreen ? NSOnState : NSOffState)];
+ [alwaysShowButton setState: (settings.shared.forcesetup ? NSOnState : NSOffState)];
+ [self populateVideoModes:YES];
+
+ // enable all the controls on the Configuration page
+ NSEnumerator *enumerator = [[[tabViewItemSetup view] subviews] objectEnumerator];
+ NSControl *control;
+ while ((control = [enumerator nextObject]))
+ {
+ if ([control respondsToSelector:@selector(setEnabled:)])
+ [control setEnabled:true];
+ }
+
+ gamelistsrc = [[GameListSource alloc] init];
+ [[gameList documentView] setDataSource:gamelistsrc];
+ [[gameList documentView] deselectAll:nil];
+
+ if (settings.ini)
+ {
+ int row = [gamelistsrc findIndexForINI:[NSString stringWithUTF8String:settings.ini->zName]];
+ if (row >= 0)
+ {
+ [[gameList documentView] scrollRowToVisible:row];
+#if defined MAC_OS_X_VERSION_10_3 && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
+ [[gameList documentView] selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
+#else
+ [[gameList documentView] selectRow:row byExtendingSelection:NO];
+#endif
+ }
+ }
+
+ [cancelButton setEnabled:true];
+ [startButton setEnabled:true];
+
+ [tabView selectTabViewItem:tabViewItemSetup];
+ [NSCursor unhide]; // Why should I need to do this?
+}
+
+- (void)setupMessagesMode
+{
+ [tabView selectTabViewItem:tabViewItemMessageLog];
+
+ // disable all the controls on the Configuration page except "always show", so the
+ // user can enable it if they want to while waiting for something else to happen
+ NSEnumerator *enumerator = [[[tabViewItemSetup view] subviews] objectEnumerator];
+ NSControl *control;
+ while ((control = [enumerator nextObject]))
+ {
+ if (control != alwaysShowButton && [control respondsToSelector:@selector(setEnabled:)])
+ [control setEnabled:false];
+ }
+
+ [cancelButton setEnabled:false];
+ [startButton setEnabled:false];
+}
+
+- (void)putsMessage:(NSString *)str
+{
+ NSRange end;
+ NSTextStorage *text = [messagesView textStorage];
+ BOOL shouldAutoScroll;
+
+ shouldAutoScroll = ((int)NSMaxY([messagesView bounds]) == (int)NSMaxY([messagesView visibleRect]));
+
+ end.location = [text length];
+ end.length = 0;
+
+ [text beginEditing];
+ [messagesView replaceCharactersInRange:end withString:str];
+ [text endEditing];
+
+ if (shouldAutoScroll) {
+ end.location = [text length];
+ end.length = 0;
+ [messagesView scrollRangeToVisible:end];
+ }
+}
+
+@end
+
+static StartupWindow *startwin = nil;
+
+int startwin_open(void)
+{
+ // fix for "ld: absolute address to symbol _NSApp in a different linkage unit not supported"
+ // (OS X 10.6) when building for PPC
+ nsapp = [NSApplication sharedApplication];
+
+ if (startwin != nil) return 1;
+
+ startwin = [[StartupWindow alloc] init];
+ if (startwin == nil) return -1;
+
+ [startwin setupMessagesMode];
+
+ [nsapp finishLaunching];
+
+ [startwin center];
+ [startwin makeKeyAndOrderFront:nil];
+
+ CreateApplicationMenus();
+
+ return 0;
+}
+
+int startwin_close(void)
+{
+ if (startwin == nil) return 1;
+
+ [startwin close];
+ [startwin release];
+ startwin = nil;
+
+ return 0;
+}
+
+int startwin_puts(const char *s)
+{
+ NSString *ns;
+
+ if (!s) return -1;
+ if (startwin == nil) return 1;
+
+ ns = [NSString stringWithUTF8String:s];
+ [startwin putsMessage:ns];
+ [ns release];
+
+ return 0;
+}
+
+int startwin_settitle(const char *s)
+{
+ NSString *ns;
+
+ if (!s) return -1;
+ if (startwin == nil) return 1;
+
+ ns = [NSString stringWithUTF8String:s];
+ [startwin setTitle:ns];
+ [ns release];
+
+ return 0;
+}
+
+int startwin_idle(void *v)
+{
+ UNREFERENCED_PARAMETER(v);
+
+ if (startwin)
+ {
+ NSEvent *event;
+ do
+ {
+ event = [nsapp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate date] inMode:NSDefaultRunLoopMode dequeue:YES];
+ [nsapp sendEvent:event];
+ }
+ while (event != nil);
+
+ [startwin displayIfNeeded];
+ [nsapp updateWindows];
+ }
+
+ return 0;
+}
+
+
+int startwin_run(void)
+{
+ if (startwin == nil) return 0;
+
+ settings.shared = gSetup;
+ settings.ini = pINISelected;
+ settings.gamedir = g_modDir;
+
+ [startwin setupRunMode];
+
+ do
+ {
+ NSEvent *event = [nsapp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
+ [nsapp sendEvent:event];
+ [nsapp updateWindows];
+ }
+ while (retval == -1);
+
+ [startwin setupMessagesMode];
+ [nsapp updateWindows];
+
+ if (retval) {
+ gSetup = settings.shared;
+ glrendmode = settings.polymer ? REND_POLYMER : REND_POLYMOST;
+ pINISelected = settings.ini;
+ Bstrcpy(g_modDir, (gNoSetup == 0 && settings.gamedir != NULL) ? settings.gamedir : "/");
+ }
+
+ return retval;
+}
diff --git a/source/blood/src/startwin.game.cpp b/source/blood/src/startwin.game.cpp
new file mode 100644
index 000000000..ab6717b2f
--- /dev/null
+++ b/source/blood/src/startwin.game.cpp
@@ -0,0 +1,719 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#ifndef _WIN32
+#error Only for Windows
+#endif
+
+#include "renderlayer.h"
+
+#ifdef STARTUP_SETUP_WINDOW
+
+#define NEED_WINDOWSX_H
+#define NEED_COMMCTRL_H
+#define ONLY_USERDEFS
+
+#include "_control.h"
+#include "build.h"
+#include "cache1d.h"
+//#include "cmdline.h"
+#include "common_game.h"
+#include "compat.h"
+#include "control.h"
+#include "config.h"
+//#include "function.h"
+//#include "game.h"
+//#include "grpscan.h"
+//#include "inv.h"
+#include "blood.h"
+#include "keyboard.h"
+#include "startwin.game.h"
+#include "windows_inc.h"
+
+#define TAB_CONFIG 0
+#define TAB_MESSAGES 1
+
+static struct
+{
+ INICHAIN const * ini;
+ char *gamedir;
+ ud_setup_t shared;
+ int polymer;
+}
+settings;
+
+static HWND startupdlg;
+static HWND pages[3];
+static int done = -1;
+static int mode = TAB_CONFIG;
+
+static CACHE1D_FIND_REC *finddirs;
+
+static inline void clearfilenames(void)
+{
+ klistfree(finddirs);
+ finddirs = NULL;
+}
+
+static inline void getfilenames(char const *path)
+{
+ clearfilenames();
+ finddirs = klistpath(path,"*",CACHE1D_FIND_DIR);
+}
+
+#define POPULATE_VIDEO 1
+#define POPULATE_CONFIG 2
+#define POPULATE_GAME 4
+#define POPULATE_GAMEDIRS 8
+
+#ifdef INPUT_MOUSE
+#undef INPUT_MOUSE
+#endif
+
+#define INPUT_KB 0
+#define INPUT_MOUSE 1
+#define INPUT_JOYSTICK 2
+#define INPUT_ALL 3
+
+const char *controlstrings[] = { "Keyboard only", "Keyboard and mouse", "Keyboard and joystick", "All supported devices" };
+
+static void PopulateForm(int32_t pgs)
+{
+ char buf[512];
+
+ if (pgs & POPULATE_GAMEDIRS)
+ {
+ HWND hwnd = GetDlgItem(pages[TAB_CONFIG], IDCGAMEDIR);
+
+ getfilenames("/");
+ (void)ComboBox_ResetContent(hwnd);
+ int const r = ComboBox_AddString(hwnd, "None");
+ (void)ComboBox_SetItemData(hwnd, r, 0);
+ (void)ComboBox_SetCurSel(hwnd, r);
+ auto dirs = finddirs;
+ for (int i=1, j=1; dirs != NULL; dirs=dirs->next)
+ {
+ if (Bstrcasecmp(dirs->name, "autoload") == 0)
+ {
+ j++;
+ continue;
+ }
+
+ (void)ComboBox_AddString(hwnd, dirs->name);
+ (void)ComboBox_SetItemData(hwnd, i, j);
+ if (Bstrcasecmp(dirs->name, settings.gamedir) == 0)
+ (void)ComboBox_SetCurSel(hwnd, i);
+
+ i++;
+ j++;
+ }
+ }
+
+ if (pgs & POPULATE_VIDEO)
+ {
+ HWND hwnd = GetDlgItem(pages[TAB_CONFIG], IDCVMODE);
+ int mode = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, settings.shared.bpp, settings.shared.fullscreen, 1);
+
+ if (mode < 0 || (settings.shared.bpp < 15 && (settings.polymer)))
+ {
+ int CONSTEXPR cd[] = { 32, 24, 16, 15, 8, 0 };
+ int i;
+
+ for (i=0; cd[i];)
+ {
+ if (cd[i] >= settings.shared.bpp) i++;
+ else break;
+ }
+ for (; cd[i]; i++)
+ {
+ mode = videoCheckMode(&settings.shared.xdim, &settings.shared.ydim, cd[i], settings.shared.fullscreen, 1);
+ if (mode < 0) continue;
+ settings.shared.bpp = cd[i];
+ break;
+ }
+ }
+
+ Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCFULLSCREEN), ((settings.shared.fullscreen) ? BST_CHECKED : BST_UNCHECKED));
+ //Button_SetCheck(GetDlgItem(pages[TAB_CONFIG], IDCPOLYMER), ((settings.polymer) ? BST_CHECKED : BST_UNCHECKED));
+
+ (void)ComboBox_ResetContent(hwnd);
+
+ for (int i=0; ipNext)
+ {
+ if (fg->pDescription)
+ Bsprintf(buf, "%s\t%s", fg->pDescription->pzName, fg->zName);
+ else
+ Bsprintf(buf, "%s", fg->zName);
+ int const j = ListBox_AddString(hwnd, buf);
+ (void)ListBox_SetItemData(hwnd, j, (LPARAM)fg);
+ if (settings.ini == fg)
+ (void)ListBox_SetCurSel(hwnd, j);
+ }
+ }
+}
+
+static INT_PTR CALLBACK ConfigPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCFULLSCREEN:
+ settings.shared.fullscreen = !settings.shared.fullscreen;
+ PopulateForm(POPULATE_VIDEO);
+ return TRUE;
+ //case IDCPOLYMER:
+ // settings.polymer = !settings.polymer;
+ // if (settings.shared.bpp == 8) settings.shared.bpp = 32;
+ // PopulateForm(POPULATE_VIDEO);
+ // return TRUE;
+ case IDCVMODE:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ int i = ComboBox_GetCurSel((HWND)lParam);
+ if (i != CB_ERR) i = ComboBox_GetItemData((HWND)lParam, i);
+ if (i != CB_ERR)
+ {
+ settings.shared.xdim = validmode[i].xdim;
+ settings.shared.ydim = validmode[i].ydim;
+ settings.shared.bpp = validmode[i].bpp;
+ }
+ }
+ return TRUE;
+ case IDCALWAYSSHOW:
+ settings.shared.forcesetup = IsDlgButtonChecked(hwndDlg, IDCALWAYSSHOW) == BST_CHECKED;
+ return TRUE;
+ case IDCAUTOLOAD:
+ settings.shared.noautoload = (IsDlgButtonChecked(hwndDlg, IDCAUTOLOAD) != BST_CHECKED);
+ return TRUE;
+ case IDCINPUT:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ int i = ComboBox_GetCurSel((HWND)lParam);
+ if (i != CB_ERR) i = ComboBox_GetItemData((HWND)lParam, i);
+ if (i != CB_ERR)
+ {
+ switch (i)
+ {
+ case INPUT_KB:
+ settings.shared.usemouse = settings.shared.usejoystick = 0;
+ break;
+ case INPUT_MOUSE:
+ settings.shared.usemouse = 1;
+ settings.shared.usejoystick = 0;
+ break;
+ case INPUT_JOYSTICK:
+ settings.shared.usemouse = 0;
+ settings.shared.usejoystick = 1;
+ break;
+ case INPUT_ALL:
+ settings.shared.usemouse = settings.shared.usejoystick = 1;
+ break;
+ }
+ }
+ }
+ return TRUE;
+
+ case IDCGAMEDIR:
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ int i = ComboBox_GetCurSel((HWND)lParam);
+ if (i != CB_ERR) i = ComboBox_GetItemData((HWND)lParam, i);
+ if (i != CB_ERR)
+ {
+ if (i==0)
+ settings.gamedir = NULL;
+ else
+ {
+ CACHE1D_FIND_REC *dir = finddirs;
+ for (int j = 1; dir != NULL; dir = dir->next, j++)
+ {
+ if (j == i)
+ {
+ settings.gamedir = dir->name;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return TRUE;
+ case IDCDATA:
+ {
+ if (HIWORD(wParam) != LBN_SELCHANGE) break;
+ intptr_t i = ListBox_GetCurSel((HWND)lParam);
+ if (i != CB_ERR) i = ListBox_GetItemData((HWND)lParam, i);
+ if (i != CB_ERR)
+ {
+ settings.ini = (INICHAIN const *)i;
+ }
+ return TRUE;
+ }
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+
+static void SetPage(int pageNum)
+{
+ HWND tab = GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL);
+ auto const cur = SendMessage(tab, TCM_GETCURSEL, 0, 0);
+ ShowWindow(pages[cur], SW_HIDE);
+ SendMessage(tab, TCM_SETCURSEL, pageNum, 0);
+ ShowWindow(pages[pageNum], SW_SHOW);
+ mode = pageNum;
+
+ SetFocus(GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL));
+}
+
+static void EnableConfig(bool n)
+{
+ //EnableWindow(GetDlgItem(startupdlg, WIN_STARTWIN_CANCEL), n);
+ EnableWindow(GetDlgItem(startupdlg, WIN_STARTWIN_START), n);
+ EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCDATA), n);
+ EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCFULLSCREEN), n);
+ EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCGAMEDIR), n);
+ EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCINPUT), n);
+ //EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCPOLYMER), n);
+ EnableWindow(GetDlgItem(pages[TAB_CONFIG], IDCVMODE), n);
+}
+
+static INT_PTR CALLBACK startup_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ static HBITMAP hbmp = NULL;
+
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ {
+ // Fetch the positions (in screen coordinates) of all the windows we need to tweak
+ RECT chrome = {};
+ AdjustWindowRect(&chrome, GetWindowLong(hwndDlg, GWL_STYLE), FALSE);
+ RECT rdlg;
+ GetWindowRect(hwndDlg, &rdlg);
+
+ // Knock off the non-client area of the main dialogue to give just the client area
+ rdlg.left -= chrome.left;
+ rdlg.top -= chrome.top;
+ rdlg.right -= chrome.right;
+ rdlg.bottom -= chrome.bottom;
+
+ RECT rtab;
+ GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL), &rtab);
+
+ // Translate them to client-relative coordinates wrt the main dialogue window
+ rtab.right -= rtab.left - 1;
+ rtab.bottom -= rtab.top - 1;
+ rtab.left -= rdlg.left;
+ rtab.top -= rdlg.top;
+
+ RECT rcancel;
+ GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_CANCEL), &rcancel);
+
+ rcancel.right -= rcancel.left - 1;
+ rcancel.bottom -= rcancel.top - 1;
+ rcancel.left -= rdlg.left;
+ rcancel.top -= rdlg.top;
+
+ RECT rstart;
+ GetWindowRect(GetDlgItem(hwndDlg, WIN_STARTWIN_START), &rstart);
+
+ rstart.right -= rstart.left - 1;
+ rstart.bottom -= rstart.top - 1;
+ rstart.left -= rdlg.left;
+ rstart.top -= rdlg.top;
+
+ // And then convert the main dialogue coordinates to just width/length
+ rdlg.right -= rdlg.left - 1;
+ rdlg.bottom -= rdlg.top - 1;
+ rdlg.left = 0;
+ rdlg.top = 0;
+
+ // Load the bitmap into the bitmap control and fetch its dimensions
+ hbmp = LoadBitmap((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(RSRC_BMP));
+
+ HWND hwnd = GetDlgItem(hwndDlg, WIN_STARTWIN_BITMAP);
+ SendMessage(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmp);
+
+ RECT r;
+ GetClientRect(hwnd, &r);
+
+ int const xoffset = r.right;
+ int const yoffset = r.bottom - rdlg.bottom;
+
+ // Shift and resize the controls that require it
+ rtab.left += xoffset;
+ rtab.bottom += yoffset;
+ rcancel.left += xoffset;
+ rcancel.top += yoffset;
+ rstart.left += xoffset;
+ rstart.top += yoffset;
+ rdlg.right += xoffset;
+ rdlg.bottom += yoffset;
+
+ // Move the controls to their new positions
+ MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL), rtab.left, rtab.top, rtab.right, rtab.bottom, FALSE);
+ MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_CANCEL), rcancel.left, rcancel.top, rcancel.right, rcancel.bottom, FALSE);
+ MoveWindow(GetDlgItem(hwndDlg, WIN_STARTWIN_START), rstart.left, rstart.top, rstart.right, rstart.bottom, FALSE);
+
+ // Move the main dialogue to the centre of the screen
+ HDC hdc = GetDC(NULL);
+ rdlg.left = (GetDeviceCaps(hdc, HORZRES) - rdlg.right) / 2;
+ rdlg.top = (GetDeviceCaps(hdc, VERTRES) - rdlg.bottom) / 2;
+ ReleaseDC(NULL, hdc);
+ MoveWindow(hwndDlg, rdlg.left + chrome.left, rdlg.top + chrome.left,
+ rdlg.right + (-chrome.left+chrome.right), rdlg.bottom + (-chrome.top+chrome.bottom), TRUE);
+
+ // Add tabs to the tab control
+ {
+ static char textSetup[] = TEXT("Setup");
+ static char textMessageLog[] = TEXT("Message Log");
+
+ hwnd = GetDlgItem(hwndDlg, WIN_STARTWIN_TABCTL);
+
+ TCITEM tab = {};
+ tab.mask = TCIF_TEXT;
+ tab.pszText = textSetup;
+ SendMessage(hwnd, TCM_INSERTITEM, (WPARAM)TAB_CONFIG, (LPARAM)&tab);
+ tab.mask = TCIF_TEXT;
+ tab.pszText = textMessageLog;
+ SendMessage(hwnd, TCM_INSERTITEM, (WPARAM)TAB_MESSAGES, (LPARAM)&tab);
+
+ // Work out the position and size of the area inside the tab control for the pages
+ ZeroMemory(&r, sizeof(r));
+ GetClientRect(hwnd, &r);
+ SendMessage(hwnd, TCM_ADJUSTRECT, FALSE, (LPARAM)&r);
+ r.right -= r.left-1;
+ r.bottom -= r.top-1;
+ r.top += rtab.top;
+ r.left += rtab.left;
+
+ // Create the pages and position them in the tab control, but hide them
+ pages[TAB_CONFIG] = CreateDialog((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(WIN_STARTWINPAGE_CONFIG), hwndDlg, ConfigPageProc);
+ SetWindowPos(pages[TAB_CONFIG], hwnd, r.left, r.top, r.right, r.bottom, SWP_HIDEWINDOW);
+
+ pages[TAB_MESSAGES] = GetDlgItem(hwndDlg, WIN_STARTWIN_MESSAGES);
+ SetWindowPos(pages[TAB_MESSAGES], hwnd, r.left, r.top, r.right, r.bottom, SWP_HIDEWINDOW);
+
+ // Tell the editfield acting as the console to exclude the width of the scrollbar
+ GetClientRect(pages[TAB_MESSAGES], &r);
+ r.right -= GetSystemMetrics(SM_CXVSCROLL)+4;
+ r.left = r.top = 0;
+ SendMessage(pages[TAB_MESSAGES], EM_SETRECTNP,0,(LPARAM)&r);
+
+ // Set a tab stop in the game data listbox
+ {
+ DWORD tabs[1] = { 150 };
+ (void)ListBox_SetTabStops(GetDlgItem(pages[TAB_CONFIG], IDCDATA), 1, tabs);
+ }
+
+ SetFocus(GetDlgItem(hwndDlg, WIN_STARTWIN_START));
+ SetWindowText(hwndDlg, apptitle);
+ }
+ return FALSE;
+ }
+
+ case WM_NOTIFY:
+ {
+ auto nmhdr = (LPNMHDR)lParam;
+ if (nmhdr->idFrom != WIN_STARTWIN_TABCTL) break;
+ int const cur = SendMessage(nmhdr->hwndFrom, TCM_GETCURSEL,0,0);
+ switch (nmhdr->code)
+ {
+ case TCN_SELCHANGING:
+ case TCN_SELCHANGE:
+ if (cur < 0 || !pages[cur])
+ break;
+ ShowWindow(pages[cur], nmhdr->code == TCN_SELCHANGING ? SW_HIDE : SW_SHOW);
+ return TRUE;
+ }
+ break;
+ }
+
+ case WM_CLOSE:
+ if (mode == TAB_CONFIG) done = 0;
+ else quitevent++;
+ return TRUE;
+
+ case WM_DESTROY:
+ if (hbmp)
+ {
+ DeleteObject(hbmp);
+ hbmp = NULL;
+ }
+
+ if (pages[TAB_CONFIG])
+ {
+ DestroyWindow(pages[TAB_CONFIG]);
+ pages[TAB_CONFIG] = NULL;
+ }
+
+ startupdlg = NULL;
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case WIN_STARTWIN_CANCEL:
+ if (mode == TAB_CONFIG) done = 0;
+ else quitevent++;
+ return TRUE;
+ case WIN_STARTWIN_START:
+ done = 1;
+ return TRUE;
+ }
+ return FALSE;
+
+ case WM_CTLCOLORSTATIC:
+ if ((HWND)lParam == pages[TAB_MESSAGES])
+ return (BOOL)(intptr_t)GetSysColorBrush(COLOR_WINDOW);
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+
+int32_t startwin_open(void)
+{
+ if (startupdlg) return 1;
+ INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_TAB_CLASSES };
+ InitCommonControlsEx(&icc);
+ startupdlg = CreateDialog((HINSTANCE)win_gethinstance(), MAKEINTRESOURCE(WIN_STARTWIN), NULL, startup_dlgproc);
+ if (startupdlg)
+ {
+ SetPage(TAB_MESSAGES);
+ EnableConfig(0);
+ return 0;
+ }
+ return -1;
+}
+
+int32_t startwin_close(void)
+{
+ if (!startupdlg) return 1;
+ DestroyWindow(startupdlg);
+ startupdlg = NULL;
+ return 0;
+}
+
+int32_t startwin_puts(const char *buf)
+{
+ if (!startupdlg) return 1;
+
+ const HWND edctl = pages[TAB_MESSAGES];
+
+ if (!edctl) return -1;
+
+ static HWND dactrl = NULL;
+ if (!dactrl) dactrl = GetDlgItem(startupdlg, WIN_STARTWIN_TABCTL);
+
+ int const vis = ((int)SendMessage(dactrl, TCM_GETCURSEL,0,0) == TAB_MESSAGES);
+
+ if (vis)
+ SendMessage(edctl, WM_SETREDRAW, FALSE, 0);
+
+ int const curlen = SendMessage(edctl, WM_GETTEXTLENGTH, 0,0);
+ SendMessage(edctl, EM_SETSEL, (WPARAM)curlen, (LPARAM)curlen);
+
+ int const numlines = SendMessage(edctl, EM_GETLINECOUNT, 0, 0);
+ static bool newline = false;
+ const char *p = buf;
+
+ while (*p)
+ {
+ if (newline)
+ {
+ SendMessage(edctl, EM_REPLACESEL, 0, (LPARAM)"\r\n");
+ newline = false;
+ }
+ const char *q = p;
+ while (*q && *q != '\n') q++;
+ static char workbuf[1024];
+ Bmemcpy(workbuf, p, q-p);
+ if (*q == '\n')
+ {
+ if (!q[1])
+ {
+ newline = true;
+ workbuf[q-p] = 0;
+ }
+ else
+ {
+ workbuf[q-p] = '\r';
+ workbuf[q-p+1] = '\n';
+ workbuf[q-p+2] = 0;
+ }
+ p = q+1;
+ }
+ else
+ {
+ workbuf[q-p] = 0;
+ p = q;
+ }
+ SendMessage(edctl, EM_REPLACESEL, 0, (LPARAM)workbuf);
+ }
+
+ int const newnumlines = SendMessage(edctl, EM_GETLINECOUNT, 0, 0);
+ SendMessage(edctl, EM_LINESCROLL, 0, newnumlines - numlines);
+
+ if (vis)
+ SendMessage(edctl, WM_SETREDRAW, TRUE, 0);
+
+ return 0;
+}
+
+int32_t startwin_settitle(const char *str)
+{
+ if (!startupdlg) return 1;
+ SetWindowText(startupdlg, str);
+ return 0;
+}
+
+int32_t startwin_idle(void *v)
+{
+ if (!startupdlg || !IsWindow(startupdlg)) return 0;
+ if (IsDialogMessage(startupdlg, (MSG *)v)) return 1;
+ return 0;
+}
+
+int32_t startwin_run(void)
+{
+ if (!startupdlg) return 1;
+
+ done = -1;
+
+ SetPage(TAB_CONFIG);
+ EnableConfig(1);
+
+#ifdef POLYMER
+ settings.polymer = (glrendmode == REND_POLYMER);
+#else
+ settings.polymer = 0;
+#endif
+
+ settings.shared = gSetup;
+ settings.ini = pINISelected;
+ settings.gamedir = g_modDir;
+
+ PopulateForm(-1);
+
+ do
+ {
+ MSG msg;
+
+ switch (GetMessage(&msg, NULL, 0,0))
+ {
+ case 0:
+ done = 1;
+ break;
+ case -1:
+ return -1;
+ default:
+ if (IsWindow(startupdlg) && IsDialogMessage(startupdlg, &msg))
+ break;
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ break;
+ }
+ }
+ while (done < 0);
+
+ SetPage(TAB_MESSAGES);
+ EnableConfig(0);
+
+ if (done)
+ {
+ gSetup = settings.shared;
+ glrendmode = (settings.polymer) ? REND_POLYMER : REND_POLYMOST;
+ pINISelected = settings.ini;
+ Bstrcpy(g_modDir, (gNoSetup == 0 && settings.gamedir != NULL) ? settings.gamedir : "/");
+ }
+
+ return done;
+}
+
+#endif // STARTUP_SETUP_WINDOW
diff --git a/source/blood/src/startwin.game.h b/source/blood/src/startwin.game.h
new file mode 100644
index 000000000..d97dc31cb
--- /dev/null
+++ b/source/blood/src/startwin.game.h
@@ -0,0 +1,48 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+// resource ids
+#define WIN_STARTWIN 1000
+#define WIN_STARTWINPAGE_CONFIG 2000
+#define WIN_STARTWIN_BITMAP 100 // banner bitmap
+#define WIN_STARTWIN_TABCTL 101
+#define WIN_STARTWIN_CANCEL IDCANCEL
+#define WIN_STARTWIN_START IDOK
+
+#define WIN_STARTWIN_MESSAGES 104 // output list box
+
+#define RSRC_ICON 100
+#define RSRC_BMP 200
+
+// config page
+#define IDCFULLSCREEN 100
+#define IDCVMODE 101
+#define IDCSOUNDDRV 102
+#define IDCMIDIDEV 103
+#define IDCCDADEV 104
+#define IDCALWAYSSHOW 105
+#define IDCDATA 106
+#define IDCGAMEDIR 107
+#define IDCPOLYMER 108
+#define IDCAUTOLOAD 109
+#define IDCINPUT 110
diff --git a/source/blood/src/tile.cpp b/source/blood/src/tile.cpp
new file mode 100644
index 000000000..dab517a14
--- /dev/null
+++ b/source/blood/src/tile.cpp
@@ -0,0 +1,278 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include "compat.h"
+#include "build.h"
+#include "common.h"
+#include "common_game.h"
+
+#include "blood.h"
+#include "config.h"
+#include "resource.h"
+#include "tile.h"
+
+void qloadvoxel(int32_t nVoxel)
+{
+ static int nLastVoxel = 0;
+ dassert(nVoxel >= 0 && nVoxel < kMaxVoxels);
+ DICTNODE *hVox = gSysRes.Lookup(nVoxel, "KVX");
+ if (!hVox)
+ ThrowError("Missing voxel #%d", nVoxel);
+ if (!hVox->lockCount)
+ voxoff[nLastVoxel][0] = 0;
+ nLastVoxel = nVoxel;
+ char *pVox = (char*)gSysRes.Lock(hVox);
+ for (int i = 0; i < MAXVOXMIPS; i++)
+ {
+ int nSize = *((int*)pVox);
+#if B_BIG_ENDIAN == 1
+ nSize = B_LITTLE32(nSize);
+#endif
+ pVox += 4;
+ voxoff[nVoxel][i] = (intptr_t)pVox;
+ pVox += nSize;
+ }
+}
+
+void CalcPicsiz(int a1, int a2, int a3)
+{
+ int nP = 0;
+ for (int i = 2; i <= a2; i<<= 1)
+ nP++;
+ for (int i = 2; i <= a3; i<<= 1)
+ nP+=1<<4;
+ picsiz[a1] = nP;
+}
+
+CACHENODE tileNode[kMaxTiles];
+
+bool artLoaded = false;
+int nTileFiles = 0;
+
+int tileStart[256];
+int tileEnd[256];
+int hTileFile[256];
+
+char surfType[kMaxTiles];
+signed char tileShade[kMaxTiles];
+short voxelIndex[kMaxTiles];
+
+const char *pzBaseFileName = "TILES%03i.ART"; //"TILES%03i.ART";
+
+int tileInit(char a1, const char *a2)
+{
+ UNREFERENCED_PARAMETER(a1);
+ if (artLoaded)
+ return 1;
+ artLoadFiles(a2 ? a2 : pzBaseFileName, MAXCACHE1DSIZE);
+ for (int i = 0; i < kMaxTiles; i++)
+ voxelIndex[i] = 0;
+
+ int hFile = kopen4loadfrommod("SURFACE.DAT", 0);
+ if (hFile != -1)
+ {
+ kread(hFile, surfType, sizeof(surfType));
+ kclose(hFile);
+ }
+ hFile = kopen4loadfrommod("VOXEL.DAT", 0);
+ if (hFile != -1)
+ {
+ kread(hFile, voxelIndex, sizeof(voxelIndex));
+#if B_BIG_ENDIAN == 1
+ for (int i = 0; i < kMaxTiles; i++)
+ voxelIndex[i] = B_LITTLE16(voxelIndex[i]);
+#endif
+ kclose(hFile);
+ }
+ hFile = kopen4loadfrommod("SHADE.DAT", 0);
+ if (hFile != -1)
+ {
+ kread(hFile, tileShade, sizeof(tileShade));
+ kclose(hFile);
+ }
+ for (int i = 0; i < kMaxTiles; i++)
+ {
+ if (voxelIndex[i] >= 0 && voxelIndex[i] < kMaxVoxels)
+ SetBitString((char*)voxreserve, voxelIndex[i]);
+ }
+
+ artLoaded = 1;
+
+ #ifdef USE_OPENGL
+ PolymostProcessVoxels_Callback = tileProcessGLVoxels;
+ #endif
+
+ return 1;
+}
+
+#ifdef USE_OPENGL
+void tileProcessGLVoxels(void)
+{
+ static bool voxInit = false;
+ if (voxInit)
+ return;
+ voxInit = true;
+ for (int i = 0; i < kMaxVoxels; i++)
+ {
+ DICTNODE *hVox = gSysRes.Lookup(i, "KVX");
+ if (!hVox)
+ continue;
+ char *pVox = (char*)gSysRes.Load(hVox);
+ voxmodels[i] = loadkvxfrombuf(pVox, hVox->size);
+ }
+}
+#endif
+
+char * tileLoadTile(int nTile)
+{
+ if (!waloff[nTile]) tileLoad(nTile);
+ return (char*)waloff[nTile];
+}
+
+char * tileAllocTile(int nTile, int x, int y, int ox, int oy)
+{
+ dassert(nTile >= 0 && nTile < kMaxTiles);
+ char *p = (char*)tileCreate(nTile, x, y);
+ dassert(p != NULL);
+ picanm[nTile].xofs = ClipRange(ox, -127, 127);
+ picanm[nTile].yofs = ClipRange(oy, -127, 127);
+ return (char*)waloff[nTile];
+}
+
+void tilePreloadTile(int nTile)
+{
+ int n = 0;
+ switch (picanm[nTile].extra&7)
+ {
+ case 0:
+ n = 1;
+ break;
+ case 1:
+ n = 5;
+ break;
+ case 2:
+ n = 8;
+ break;
+ case 3:
+ n = 2;
+ break;
+ case 6:
+ case 7:
+ if (voxelIndex[nTile] < 0 || voxelIndex[nTile] >= kMaxVoxels)
+ {
+ voxelIndex[nTile] = -1;
+ picanm[nTile].extra &= ~7;
+ }
+ else
+ qloadvoxel(voxelIndex[nTile]);
+ break;
+ }
+ while(n--)
+ {
+ if (picanm[nTile].sf&PICANM_ANIMTYPE_MASK)
+ {
+ for (int frame = picanm[nTile].num; frame >= 0; frame--)
+ {
+ if ((picanm[nTile].sf&PICANM_ANIMTYPE_MASK) == PICANM_ANIMTYPE_BACK)
+ tileLoadTile(nTile-frame);
+ else
+ tileLoadTile(nTile+frame);
+ }
+ }
+ else
+ tileLoadTile(nTile);
+ nTile += 1+picanm[nTile].num;
+ }
+}
+
+extern int nPrecacheCount;
+extern char precachehightile[2][(MAXTILES+7)>>3];
+
+void tilePrecacheTile(int nTile, int nType)
+{
+ int n = 0;
+ switch (picanm[nTile].extra&7)
+ {
+ case 0:
+ n = 1;
+ break;
+ case 1:
+ n = 5;
+ break;
+ case 2:
+ n = 8;
+ break;
+ case 3:
+ n = 2;
+ break;
+ }
+ while(n--)
+ {
+ if (picanm[nTile].sf&PICANM_ANIMTYPE_MASK)
+ {
+ for (int frame = picanm[nTile].num; frame >= 0; frame--)
+ {
+ int tile;
+ if ((picanm[nTile].sf&PICANM_ANIMTYPE_MASK) == PICANM_ANIMTYPE_BACK)
+ tile = nTile-frame;
+ else
+ tile = nTile+frame;
+ if (!TestBitString(gotpic, tile))
+ {
+ nPrecacheCount++;
+ SetBitString(gotpic, tile);
+ }
+ SetBitString(precachehightile[nType], tile);
+ }
+ }
+ else
+ {
+ if (!TestBitString(gotpic, nTile))
+ {
+ nPrecacheCount++;
+ SetBitString(gotpic, nTile);
+ }
+ SetBitString(precachehightile[nType], nTile);
+ }
+ nTile += 1+picanm[nTile].num;
+ }
+}
+
+char tileGetSurfType(int hit)
+{
+ int n = hit &0x1fff;
+ switch (hit&0xe000)
+ {
+ case 0x4000:
+ return surfType[sector[n].floorpicnum];
+ case 0x6000:
+ return surfType[sector[n].ceilingpicnum];
+ case 0x8000:
+ return surfType[wall[n].picnum];
+ case 0xc000:
+ return surfType[sprite[n].picnum];
+ }
+ return 0;
+}
diff --git a/source/blood/src/tile.h b/source/blood/src/tile.h
new file mode 100644
index 000000000..3a9261ca4
--- /dev/null
+++ b/source/blood/src/tile.h
@@ -0,0 +1,61 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common_game.h"
+#include "blood.h"
+
+enum SurfaceType {
+ kSurf0 = 0,
+ kSurf1,
+ kSurf2,
+ kSurf3,
+ kSurf4,
+ kSurf5,
+ kSurf6,
+ kSurf7,
+ kSurf8,
+ kSurf9,
+ kSurf10,
+ kSurf11,
+ kSurf12,
+ kSurf13,
+ kSurf14,
+ kSurfMax
+};
+
+extern char surfType[kMaxTiles];
+extern signed char tileShade[kMaxTiles];
+extern short voxelIndex[kMaxTiles];
+
+void qloadvoxel(int32_t nVoxel);
+void CalcPicsiz(int a1, int a2, int a3);
+int tileInit(char a1, const char *a2);
+#ifdef USE_OPENGL
+void tileProcessGLVoxels(void);
+#endif
+char * tileLoadTile(int nTile);
+char * tileAllocTile(int nTile, int x, int y, int ox, int oy);
+void tilePreloadTile(int nTile);
+void tilePrecacheTile(int nTile, int nType = 1);
+char tileGetSurfType(int hit);
diff --git a/source/blood/src/trig.cpp b/source/blood/src/trig.cpp
new file mode 100644
index 000000000..0fd0b4115
--- /dev/null
+++ b/source/blood/src/trig.cpp
@@ -0,0 +1,78 @@
+#include "build.h"
+#include "pragmas.h"
+#include "common_game.h"
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "resource.h"
+#include "trig.h"
+
+int costable[2048];
+
+int OctantTable[8] = { 5, 6, 2, 1, 4, 7, 3, 0 };
+
+int GetOctant(int x, int y)
+{
+ int vc = klabs(x)-klabs(y);
+ return OctantTable[7-(x<0)-(y<0)*2-(vc<0)*4];
+}
+
+void RotateVector(int *dx, int *dy, int nAngle)
+{
+ int ox = *dx;
+ int oy = *dy;
+ *dx = dmulscale30r(ox, Cos(nAngle), -oy, Sin(nAngle));
+ *dy = dmulscale30r(ox, Sin(nAngle), oy, Cos(nAngle));
+}
+
+void RotatePoint(int *x, int *y, int nAngle, int ox, int oy)
+{
+ int dx = *x-ox;
+ int dy = *y-oy;
+ *x = ox+dmulscale30r(dx, Cos(nAngle), -dy, Sin(nAngle));
+ *y = oy+dmulscale30r(dx, Sin(nAngle), dy, Cos(nAngle));
+}
+
+void trigInit(Resource &Res)
+{
+ DICTNODE *pTable = Res.Lookup("cosine","dat");
+ if (!pTable)
+ ThrowError("Cosine table not found");
+ if (pTable->size != 2048)
+ ThrowError("Cosine table incorrect size");
+ memcpy(costable, Res.Load(pTable), pTable->size);
+#if B_BIG_ENDIAN == 1
+ for (int i = 0; i < 512; i++)
+ {
+ costable[i] = B_LITTLE32(costable[i]);
+ }
+#endif
+ costable[512] = 0;
+ for (int i = 513; i <= 1024; i++)
+ {
+ costable[i] = -costable[1024-i];
+ }
+ for (int i = 1025; i < 2048; i++)
+ {
+ costable[i] = costable[2048 - i];
+ }
+}
diff --git a/source/blood/src/trig.h b/source/blood/src/trig.h
new file mode 100644
index 000000000..96f882d43
--- /dev/null
+++ b/source/blood/src/trig.h
@@ -0,0 +1,42 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "resource.h"
+
+extern int costable[2048];
+
+int GetOctant(int x, int y);
+void RotateVector(int *dx, int *dy, int nAngle);
+void RotatePoint(int *x, int *y, int nAngle, int ox, int oy);
+void trigInit(Resource &Res);
+
+inline int Sin(int ang)
+{
+ return costable[(ang - 512) & 2047];
+}
+
+inline int Cos(int ang)
+{
+ return costable[ang & 2047];
+}
\ No newline at end of file
diff --git a/source/blood/src/triggers.cpp b/source/blood/src/triggers.cpp
new file mode 100644
index 000000000..d7f583a11
--- /dev/null
+++ b/source/blood/src/triggers.cpp
@@ -0,0 +1,4033 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+
+#include "build.h"
+#include "compat.h"
+#include "mmulti.h"
+#include "common_game.h"
+
+#include "ai.h"
+#include "actor.h"
+#include "blood.h"
+#include "db.h"
+#include "endgame.h"
+#include "eventq.h"
+
+#include "aiunicult.h"
+#include "fx.h"
+#include "gameutil.h"
+#include "gib.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "player.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "triggers.h"
+#include "trig.h"
+#include "view.h"
+
+int basePath[kMaxSectors];
+
+void FireballTrapSeqCallback(int, int);
+void UniMissileTrapSeqCallback(int, int);
+void MGunFireSeqCallback(int, int);
+void MGunOpenSeqCallback(int, int);
+
+int nFireballTrapClient = seqRegisterClient(FireballTrapSeqCallback);
+int nUniMissileTrapClient = seqRegisterClient(UniMissileTrapSeqCallback);
+int nMGunFireClient = seqRegisterClient(MGunFireSeqCallback);
+int nMGunOpenClient = seqRegisterClient(MGunOpenSeqCallback);
+
+unsigned int GetWaveValue(unsigned int nPhase, int nType)
+{
+ switch (nType)
+ {
+ case 0:
+ return 0x8000-(Cos((nPhase<<10)>>16)>>15);
+ case 1:
+ return nPhase;
+ case 2:
+ return 0x10000-(Cos((nPhase<<9)>>16)>>14);
+ case 3:
+ return Sin((nPhase<<9)>>16)>>14;
+ }
+ return nPhase;
+}
+
+char SetSpriteState(int nSprite, XSPRITE *pXSprite, int nState)
+{
+ if ((pXSprite->busy&0xffff) == 0 && pXSprite->state == nState)
+ return 0;
+ pXSprite->busy = nState<<16;
+ pXSprite->state = nState;
+ evKill(nSprite, 3);
+ if ((sprite[nSprite].hitag & 16) != 0 && sprite[nSprite].type >= kDudeBase && sprite[nSprite].type < kDudeMax)
+ {
+ pXSprite->respawnPending = 3;
+ evPost(nSprite, 3, gGameOptions.nMonsterRespawnTime, CALLBACK_ID_9);
+ return 1;
+ }
+ if (pXSprite->restState != nState && pXSprite->waitTime > 0)
+ evPost(nSprite, 3, (pXSprite->waitTime*120) / 10, pXSprite->restState ? COMMAND_ID_1 : COMMAND_ID_0);
+ if (pXSprite->txID)
+ {
+ if (pXSprite->command != 5 && pXSprite->triggerOn && pXSprite->state)
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
+ if (pXSprite->command != 5 && pXSprite->triggerOff && !pXSprite->state)
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
+ }
+ return 1;
+}
+
+char SetWallState(int nWall, XWALL *pXWall, int nState)
+{
+ if ((pXWall->busy&0xffff) == 0 && pXWall->state == nState)
+ return 0;
+ pXWall->busy = nState<<16;
+ pXWall->state = nState;
+ evKill(nWall, 0);
+ if (pXWall->restState != nState && pXWall->waitTime > 0)
+ evPost(nWall, 0, (pXWall->waitTime*120) / 10, pXWall->restState ? COMMAND_ID_1 : COMMAND_ID_0);
+ if (pXWall->txID)
+ {
+ if (pXWall->command != 5 && pXWall->triggerOn && pXWall->state)
+ evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command);
+ if (pXWall->command != 5 && pXWall->triggerOff && !pXWall->state)
+ evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command);
+ }
+ return 1;
+}
+
+char SetSectorState(int nSector, XSECTOR *pXSector, int nState)
+{
+ if ((pXSector->busy&0xffff) == 0 && pXSector->state == nState)
+ return 0;
+ pXSector->busy = nState<<16;
+ pXSector->state = nState;
+ evKill(nSector, 6);
+ if (nState == 1)
+ {
+ if (pXSector->command != 5 && pXSector->triggerOn && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command);
+ if (pXSector->at1b_2)
+ {
+ pXSector->at1b_2 = 0;
+ pXSector->at1b_3 = 0;
+ }
+ else if (pXSector->atf_6)
+ evPost(nSector, 6, (pXSector->waitTimeA * 120) / 10, COMMAND_ID_0);
+ }
+ else
+ {
+ if (pXSector->command != 5 && pXSector->triggerOff && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command);
+ if (pXSector->at1b_3)
+ {
+ pXSector->at1b_2 = 0;
+ pXSector->at1b_3 = 0;
+ }
+ else if (pXSector->atf_7)
+ evPost(nSector, 6, (pXSector->waitTimeB * 120) / 10, COMMAND_ID_1);
+ }
+ return 1;
+}
+
+int gBusyCount = 0;
+
+enum BUSYID {
+ BUSYID_0 = 0,
+ BUSYID_1,
+ BUSYID_2,
+ BUSYID_3,
+ BUSYID_4,
+ BUSYID_5,
+ BUSYID_6,
+ BUSYID_7,
+};
+
+struct BUSY {
+ int at0;
+ int at4;
+ int at8;
+ BUSYID atc;
+};
+
+BUSY gBusy[128];
+
+void AddBusy(int a1, BUSYID a2, int nDelta)
+{
+ dassert(nDelta != 0);
+ int i;
+ for (i = 0; i < gBusyCount; i++)
+ {
+ if (gBusy[i].at0 == a1 && gBusy[i].atc == a2)
+ break;
+ }
+ if (i == gBusyCount)
+ {
+ if (gBusyCount == 128)
+ return;
+ gBusy[i].at0 = a1;
+ gBusy[i].atc = a2;
+ gBusy[i].at8 = nDelta > 0 ? 0 : 65536;
+ gBusyCount++;
+ }
+ gBusy[i].at4 = nDelta;
+}
+
+void ReverseBusy(int a1, BUSYID a2)
+{
+ int i;
+ for (i = 0; i < gBusyCount; i++)
+ {
+ if (gBusy[i].at0 == a1 && gBusy[i].atc == a2)
+ {
+ gBusy[i].at4 = -gBusy[i].at4;
+ break;
+ }
+ }
+}
+
+unsigned int GetSourceBusy(EVENT a1)
+{
+ int nIndex = a1.index;
+ switch (a1.type)
+ {
+ case 6:
+ {
+ int nXIndex = sector[nIndex].extra;
+ dassert(nXIndex > 0 && nXIndex < kMaxXSectors);
+ return xsector[nXIndex].busy;
+ }
+ case 0:
+ {
+ int nXIndex = wall[nIndex].extra;
+ dassert(nXIndex > 0 && nXIndex < kMaxXWalls);
+ return xwall[nXIndex].busy;
+ }
+ case 3:
+ {
+ int nXIndex = sprite[nIndex].extra;
+ dassert(nXIndex > 0 && nXIndex < kMaxXSprites);
+ return xsprite[nXIndex].busy;
+ }
+ }
+ return 0;
+}
+
+void sub_43CF8(spritetype *pSprite, XSPRITE *pXSprite, EVENT a3)
+{
+ switch (a3.cmd)
+ {
+ case 30:
+ {
+ int nPlayer = pXSprite->data4;
+ if (nPlayer >= 0 && nPlayer < gNetPlayers)
+ {
+ PLAYER *pPlayer = &gPlayer[nPlayer];
+ if (pPlayer->pXSprite->health > 0)
+ {
+ pPlayer->at181[8] = ClipHigh(pPlayer->at181[8]+pXSprite->data3, gAmmoInfo[8].at0);
+ pPlayer->atcb[9] = 1;
+ if (pPlayer->atbd != 9)
+ {
+ pPlayer->atc3 = 0;
+ pPlayer->atbe = 9;
+ }
+ evKill(pSprite->index, 3);
+ }
+ }
+ break;
+ }
+ case 35:
+ {
+ int nTarget = pXSprite->target;
+ if (nTarget >= 0 && nTarget < kMaxSprites)
+ {
+ if (!pXSprite->stateTimer)
+ {
+ spritetype *pTarget = &sprite[nTarget];
+ if (pTarget->statnum == 6 && !(pTarget->hitag&32) && pTarget->extra > 0 && pTarget->extra < kMaxXSprites)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int nType = pTarget->type-kDudeBase;
+ DUDEINFO *pDudeInfo = &dudeInfo[nType];
+ int z1 = (top-pSprite->z)-256;
+ int x = pTarget->x;
+ int y = pTarget->y;
+ int z = pTarget->z;
+ int nDist = approxDist(x - pSprite->x, y - pSprite->y);
+ if (nDist != 0 && cansee(pSprite->x, pSprite->y, top, pSprite->sectnum, x, y, z, pTarget->sectnum))
+ {
+ int t = divscale(nDist, 0x1aaaaa, 12);
+ x += (xvel[nTarget]*t)>>12;
+ y += (yvel[nTarget]*t)>>12;
+ int angBak = pSprite->ang;
+ pSprite->ang = getangle(x-pSprite->x, y-pSprite->y);
+ int dx = Cos(pSprite->ang)>>16;
+ int dy = Sin(pSprite->ang)>>16;
+ int tz = pTarget->z - (pTarget->yrepeat * pDudeInfo->aimHeight) * 4;
+ int dz = divscale(tz - top - 256, nDist, 10);
+ int nMissileType = 316+(pXSprite->data3 ? 1 : 0);
+ int t2;
+ if (!pXSprite->data3)
+ t2 = 120 / 10.0;
+ else
+ t2 = (3*120) / 10.0;
+ spritetype *pMissile = actFireMissile(pSprite, 0, z1, dx, dy, dz, nMissileType);
+ if (pMissile)
+ {
+ pMissile->owner = pSprite->owner;
+ pXSprite->stateTimer = 1;
+ evPost(pSprite->index, 3, t2, CALLBACK_ID_20);
+ pXSprite->data3 = ClipLow(pXSprite->data3-1, 0);
+ }
+ pSprite->ang = angBak;
+ }
+ }
+ }
+ }
+ return;
+ }
+ }
+ actPostSprite(pSprite->index, kStatFree);
+}
+
+void ActivateGenerator(int);
+
+void OperateSprite(int nSprite, XSPRITE *pXSprite, EVENT a3)
+{
+ spritetype *pSprite = &sprite[nSprite];
+ switch (a3.cmd)
+ {
+ case 6:
+ pXSprite->locked = 1;
+ switch (pSprite->type) {
+ case kGDXWindGenerator:
+ stopWindOnSectors(pXSprite);
+ break;
+ }
+ return;
+ case 7:
+ pXSprite->locked = 0;
+ return;
+ case 8:
+ pXSprite->locked = pXSprite->locked ^ 1;
+ switch(pSprite->type) {
+ case kGDXWindGenerator:
+ if (pXSprite->locked == 1) stopWindOnSectors(pXSprite);
+ break;
+ }
+ return;
+ }
+ if (pSprite->statnum == 6 && pSprite->type >= kDudeBase && pSprite->type < kDudeMax)
+ {
+ switch (a3.cmd)
+ {
+ case 0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case 35:
+ if (pXSprite->state)
+ break;
+ fallthrough__;
+ case 1:
+ case 30:
+ case 33:
+ if (!pXSprite->state)
+ SetSpriteState(nSprite, pXSprite, 1);
+ aiActivateDude(pSprite, pXSprite);
+ break;
+ }
+ return;
+ }
+ switch (pSprite->type)
+ {
+
+ /* - Random Event Switch takes random data field and uses it as TX ID - */
+ /* - ranged TX ID is now supported also - */
+ case kGDXRandomTX:
+ {
+ std::default_random_engine rng; int tx = 0;
+ // set range of TX ID if data2 and data3 is empty.
+ if (pXSprite->data1 > 0 && pXSprite->data2 <= 0 && pXSprite->data3 <= 0 && pXSprite->data4 > 0) {
+
+ // data1 must be less than data4
+ if (pXSprite->data1 > pXSprite->data4) {
+ int tmp = pXSprite->data1;
+ pXSprite->data1 = pXSprite->data4;
+ pXSprite->data4 = tmp;
+ }
+
+ int total = pXSprite->data4 - pXSprite->data1;
+ int data1 = pXSprite->data1; int result = 0;
+
+ // use true random only for single player mode
+ if (gGameOptions.nGameType == 0 && !VanillaMode() && !DemoRecordStatus()) {
+ rng.seed(std::random_device()());
+ pXSprite->txID = (int)my_random(pXSprite->data1, pXSprite->data4);
+
+ // otherwise use Blood's default one. In the future it maybe possible to make
+ // host send info to clients about what was generated.
+ } else {
+ pXSprite->txID = Random(total) + data1;
+ }
+
+ } else if ((tx = GetRandDataVal(NULL,pSprite)) > 0) {
+ pXSprite->txID = tx;
+ }
+
+ switch (a3.cmd)
+ {
+ case COMMAND_ID_0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case COMMAND_ID_1:
+ SetSpriteState(nSprite, pXSprite, 1);
+ break;
+ default:
+ SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
+ break;
+ }
+
+ break;
+ }
+ /* - Sequential Switch takes values from data fields starting from data1 and uses it as TX ID - */
+ /* - ranged TX ID is now supported also - */
+ case kGDXSequentialTX:
+ {
+ bool range = false; int cnt = 3; int tx = 0;
+ // set range of TX ID if data2 and data3 is empty.
+ if (pXSprite->data1 > 0 && pXSprite->data2 <= 0 && pXSprite->data3 <= 0 && pXSprite->data4 > 0) {
+
+ // data1 must be less than data4
+ if (pXSprite->data1 > pXSprite->data4) {
+ int tmp = pXSprite->data1;
+ pXSprite->data1 = (short)pXSprite->data4;
+ pXSprite->data4 = tmp;
+ }
+
+ // force send command to all TX id in a range
+ if (pSprite->hitag == 1) {
+ for (int i = pXSprite->data1; i <= pXSprite->data4; i++) {
+ evSend(nSprite, 3, i, (COMMAND_ID) pXSprite->command);
+ }
+
+ pXSprite->txIndex = 0;
+ SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
+ break;
+ }
+
+ // Make sure txIndex is correct as we store current index of TX ID here.
+ if (pXSprite->txIndex < pXSprite->data1) pXSprite->txIndex = pXSprite->data1;
+ else if (pXSprite->txIndex > pXSprite->data4) pXSprite->txIndex = pXSprite->data4;
+
+ range = true;
+
+ } else {
+ // Make sure txIndex is correct as we store current index of data field here.
+ if (pXSprite->txIndex > 3) pXSprite->txIndex = 0;
+ else if (pXSprite->txIndex < 0) pXSprite->txIndex = 3;
+ }
+
+ switch (a3.cmd) {
+ case COMMAND_ID_0:
+ if (range == false) {
+ while (cnt-- >= 0) { // skip empty data fields
+ pXSprite->txIndex--;
+ if (pXSprite->txIndex < 0) pXSprite->txIndex = 3;
+ tx = GetDataVal(pSprite, pXSprite->txIndex);
+ if (tx < 0) ThrowError(" -- Current data index is negative");
+ if (tx > 0) break;
+ continue;
+ }
+ } else {
+ pXSprite->txIndex--;
+ if (pXSprite->txIndex < pXSprite->data1) {
+ pXSprite->txIndex = pXSprite->data4;
+ }
+ tx = pXSprite->txIndex;
+ }
+ break;
+
+ default:
+ if (range == false) {
+ while (cnt-- >= 0) { // skip empty data fields
+ if (pXSprite->txIndex > 3) pXSprite->txIndex = 0;
+ tx = GetDataVal(pSprite, pXSprite->txIndex);
+ if (tx < 0) ThrowError(" ++ Current data index is negative");
+ pXSprite->txIndex++;
+ if (tx > 0) break;
+ continue;
+ }
+ } else {
+ tx = pXSprite->txIndex;
+ if (pXSprite->txIndex >= pXSprite->data4) {
+ pXSprite->txIndex = pXSprite->data1;
+ break;
+ }
+ pXSprite->txIndex++;
+ }
+ break;
+ }
+
+ pXSprite->txID = (short)tx;
+ SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
+ break;
+ }
+ case 413:
+ if (pXSprite->health > 0)
+ {
+ if (a3.cmd == 1)
+ {
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ {
+ seqSpawn(38, 3, pSprite->extra, nMGunOpenClient);
+ if (pXSprite->data1 > 0)
+ pXSprite->data2 = pXSprite->data1;
+ }
+ }
+ else if (a3.cmd == 0)
+ {
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ seqSpawn(40, 3, pSprite->extra, -1);
+ }
+ }
+ break;
+ case 414:
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ pSprite->hitag |= 7;
+ break;
+ case 408:
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ actPostSprite(nSprite, kStatFree);
+ break;
+ case 405:
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ actPostSprite(nSprite, kStatFree);
+ break;
+ case 456:
+ switch (a3.cmd)
+ {
+ case 0:
+ pXSprite->state = 0;
+ pSprite->cstat |= 32768;
+ pSprite->cstat &= ~1;
+ break;
+ case 1:
+ pXSprite->state = 1;
+ pSprite->cstat &= (unsigned short)~32768;
+ pSprite->cstat |= 1;
+ break;
+ case 3:
+ pXSprite->state ^= 1;
+ pSprite->cstat ^= 32768;
+ pSprite->cstat ^= 1;
+ break;
+ }
+ break;
+ case 452:
+ if (a3.cmd == 1)
+ {
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ {
+ seqSpawn(38, 3, pSprite->extra, -1);
+ sfxPlay3DSound(pSprite, 441, 0, 0);
+ }
+ }
+ else if (a3.cmd == 0)
+ {
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ {
+ seqSpawn(40, 3, pSprite->extra, -1);
+ sfxKill3DSound(pSprite, 0, -1);
+ }
+ }
+ break;
+ case 23:
+ switch (a3.cmd)
+ {
+ case 0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case 1:
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ seqSpawn(37, 3, pSprite->extra, -1);
+ break;
+ default:
+ SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
+ if (pXSprite->state)
+ seqSpawn(37, 3, pSprite->extra, -1);
+ break;
+ }
+ break;
+ case 20:
+ switch (a3.cmd)
+ {
+ case 0:
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
+ break;
+ case 1:
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
+ break;
+ default:
+ if (SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1))
+ {
+ if (pXSprite->state)
+ sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
+ else
+ sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
+ }
+ break;
+ }
+ break;
+ case 21:
+ switch (a3.cmd)
+ {
+ case 0:
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
+ break;
+ case 1:
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
+ break;
+ default:
+ if (SetSpriteState(nSprite, pXSprite, pXSprite->restState ^ 1))
+ {
+ if (pXSprite->state)
+ sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
+ else
+ sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
+ }
+ break;
+ }
+ break;
+ // By NoOne: add linking for path markers and stacks feature
+ case kMarkerLowWater:
+ case kMarkerUpWater:
+ case kMarkerUpGoo:
+ case kMarkerLowGoo:
+ case kMarkerUpLink:
+ case kMarkerLowLink:
+ case kMarkerUpStack:
+ case kMarkerLowStack:
+ case kMarkerPath:
+ if (pXSprite->command == 5 && pXSprite->txID != 0)
+ evSend(nSprite, 3, pXSprite->txID, COMMAND_ID_5);
+ break;
+ case 22:
+ switch (a3.cmd)
+ {
+ case 0:
+ pXSprite->data1--;
+ if (pXSprite->data1 < 0)
+ pXSprite->data1 += pXSprite->data3;
+ break;
+ default:
+ pXSprite->data1++;
+ if (pXSprite->data1 >= pXSprite->data3)
+ pXSprite->data1 -= pXSprite->data3;
+ break;
+ }
+ if (pXSprite->command == 5 && pXSprite->txID)
+ evSend(nSprite, 3, pXSprite->txID, COMMAND_ID_5);
+ sfxPlay3DSound(pSprite, pXSprite->data4, -1, 0);
+ if (pXSprite->data1 == pXSprite->data2)
+ SetSpriteState(nSprite, pXSprite, 1);
+ else
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case kGDXObjPropertiesChanger:
+ case kGDXObjPicnumChanger:
+ case kGDXObjSizeChanger:
+ case kGDXSectorFXChanger:
+ case kGDXObjDataChanger:
+ // by NoOne: Sending new command instead of link is *required*, because types above
+ //are universal and can paste properties in different objects.
+ if (pXSprite->command == COMMAND_ID_5)
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ else {
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste); // send first command to change properties
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID) pXSprite->command); // then send normal command
+ }
+ break;
+ // this type damages sprite with given damageType
+ case kGDXSpriteDamager:
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandSpriteDamage);
+ break;
+ case 40: // Random weapon
+ case 80: // Random ammo
+ // let's first search for previously dropped items and remove it
+ if (pXSprite->dropMsg > 0) {
+ for (short nItem = headspritestat[3]; nItem >= 0; nItem = nextspritestat[nItem]) {
+ spritetype* pItem = &sprite[nItem];
+ if (pItem->lotag == pXSprite->dropMsg && pItem->x == pSprite->x && pItem->y == pSprite->y && pItem->z == pSprite->z) {
+ gFX.fxSpawn((FX_ID) 29, pSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, 0);
+ deletesprite(nItem);
+ break;
+ }
+ }
+ }
+ // then drop item
+ DropRandomPickupObject(pSprite, pXSprite->dropMsg);
+ break;
+ case kGDXCustomDudeSpawn:
+ if (gGameOptions.nMonsterSettings && actSpawnCustomDude(pSprite, -1) != NULL)
+ gKillMgr.sub_263E0(1);
+ break;
+ case 18:
+ if (gGameOptions.nMonsterSettings && pXSprite->data1 >= kDudeBase && pXSprite->data1 < kDudeMax)
+ {
+
+ spritetype* pSpawn = NULL;
+ // By NoOne: add spawn random dude feature - works only if at least 2 data fields are not empty.
+ if (!VanillaMode()) {
+ if ((pSpawn = spawnRandomDude(pSprite)) == NULL)
+ pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0);
+ } else {
+ pSpawn = actSpawnDude(pSprite, pXSprite->data1, -1, 0);
+ }
+
+ if (pSpawn)
+ {
+ XSPRITE *pXSpawn = &xsprite[pSpawn->extra];
+ gKillMgr.sub_263E0(1);
+ switch (pXSprite->data1)
+ {
+ case 239:
+ case 240:
+ case 242:
+ case 252:
+ case 253:
+ {
+ pXSpawn->health = dudeInfo[pXSprite->data1 - kDudeBase].startHealth << 4;
+ pXSpawn->burnTime = 10;
+ pXSpawn->target = -1;
+ aiActivateDude(pSpawn, pXSpawn);
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case 19:
+ pXSprite->triggerOn = 0;
+ pXSprite->isTriggered = 1;
+ SetSpriteState(nSprite, pXSprite, 1);
+ for (int p = connecthead; p >= 0; p = connectpoint2[p])
+ {
+ spritetype *pPlayerSprite = gPlayer[p].pSprite;
+ int dx = (pSprite->x - pPlayerSprite->x)>>4;
+ int dy = (pSprite->y - pPlayerSprite->y)>>4;
+ int dz = (pSprite->z - pPlayerSprite->z)>>8;
+ int nDist = dx*dx+dy*dy+dz*dz+0x40000;
+ gPlayer[p].at37f = divscale16(pXSprite->data1, nDist);
+ }
+ break;
+ case 400:
+ if (pSprite->hitag&16)
+ return;
+ fallthrough__;
+ case 418:
+ case 419:
+ case 420:
+ actExplodeSprite(pSprite);
+ break;
+ case 459:
+ switch (a3.cmd)
+ {
+ case 1:
+ SetSpriteState(nSprite, pXSprite, 1);
+ break;
+ default:
+ pSprite->cstat &= (unsigned short)~32768;
+ actExplodeSprite(pSprite);
+ break;
+ }
+ break;
+ case kGDXSeqSpawner:
+ case kGDXEffectSpawner:
+ switch (a3.cmd) {
+ case COMMAND_ID_0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case COMMAND_ID_1:
+ if (pXSprite->state == 1) break;
+ fallthrough__;
+ case COMMAND_ID_21:
+ SetSpriteState(nSprite, pXSprite, 1);
+ if (pXSprite->txID <= 0)
+ (pSprite->type == kGDXSeqSpawner) ? useSeqSpawnerGen(pXSprite, NULL) : useEffectGen(pXSprite, NULL);
+ else if (pXSprite->command == COMMAND_ID_5)
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ else {
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID) pXSprite->command);
+ }
+
+ if (pXSprite->busyTime > 0)
+ evPost(nSprite, 3, ClipLow((int(pXSprite->busyTime) + Random2(pXSprite->data1)) * 120 / 10, 0), COMMAND_ID_21);
+ break;
+
+ case COMMAND_ID_3:
+ if (pXSprite->state == 0) evPost(nSprite, 3, 0, COMMAND_ID_21);
+ else evPost(nSprite, 3, 0, COMMAND_ID_0);
+ break;
+ }
+
+ break;
+ case 402:
+ if (pSprite->statnum == 8)
+ break;
+ if (a3.cmd != 1)
+ actExplodeSprite(pSprite);
+ else
+ {
+ sfxPlay3DSound(pSprite, 454, 0, 0);
+ evPost(nSprite, 3, 18, COMMAND_ID_0);
+ }
+ break;
+ case 401:
+ case kGDXThingTNTProx:
+ if (pSprite->statnum == 8)
+ break;
+ switch (a3.cmd)
+ {
+ case 35:
+ if (!pXSprite->state)
+ {
+ sfxPlay3DSound(pSprite, 452, 0, 0);
+ evPost(nSprite, 3, 30, COMMAND_ID_0);
+ pXSprite->state = 1;
+ }
+ break;
+ case 1:
+ sfxPlay3DSound(pSprite, 451, 0, 0);
+ pXSprite->Proximity = 1;
+ break;
+ default:
+ actExplodeSprite(pSprite);
+ break;
+ }
+ break;
+ case 431:
+ sub_43CF8(pSprite, pXSprite, a3);
+ break;
+ case kGDXThingCustomDudeLifeLeech:
+ dudeLeechOperate(pSprite, pXSprite, a3);
+ break;
+ case kGDXWindGenerator:
+ switch (a3.cmd) {
+ case COMMAND_ID_0:
+ stopWindOnSectors(pXSprite);
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case COMMAND_ID_1:
+ if (pXSprite->state == 1) break;
+ fallthrough__;
+ case COMMAND_ID_21:
+ SetSpriteState(nSprite, pXSprite, 1);
+ if (pXSprite->txID <= 0) useSectorWindGen(pXSprite, NULL);
+ else if (pXSprite->command == COMMAND_ID_5)
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ else {
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID) pXSprite->command);
+ }
+
+ if (pXSprite->busyTime > 0)
+ evPost(nSprite, 3, pXSprite->busyTime, COMMAND_ID_21);
+ break;
+ case COMMAND_ID_3:
+ if (pXSprite->state == 0) evPost(nSprite, 3, 0, COMMAND_ID_21);
+ else evPost(nSprite, 3, 0, COMMAND_ID_0);
+ break;
+ }
+
+ break;
+ case kGDXDudeTargetChanger:
+ {
+ // this one is required if data4 of generator was dynamically changed
+ // it turns monsters in normal idle state instead of genIdle, so they
+ // not ignore the world.
+ bool activated = false;
+ if (pXSprite->dropMsg == 3 && 3 != pXSprite->data4) {
+ activateDudes(pXSprite->txID);
+ activated = true;
+ }
+
+ switch (a3.cmd)
+ {
+ case COMMAND_ID_0:
+ if (pXSprite->data4 == 3 && activated == false) activateDudes(pXSprite->txID);
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case COMMAND_ID_1:
+ if (pXSprite->state == 1) break;
+ fallthrough__;
+ case COMMAND_ID_21:
+ SetSpriteState(nSprite, pXSprite, 1);
+ if (pXSprite->txID <= 0 || !getDudesForTargetChg(pXSprite)) {
+ evPost(nSprite, 3, 0, COMMAND_ID_0);
+ break;
+ }
+ else if (pXSprite->command == COMMAND_ID_5)
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ else {
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
+ }
+
+ if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, COMMAND_ID_21);
+ break;
+ case COMMAND_ID_3:
+ if (pXSprite->state == 0) evPost(nSprite, 3, 0, COMMAND_ID_21);
+ else evPost(nSprite, 3, 0, COMMAND_ID_0);
+ break;
+ }
+
+ pXSprite->dropMsg = (short)pXSprite->data4;
+ break;
+ }
+ case kGDXObjDataAccumulator:
+ switch (a3.cmd) {
+ case COMMAND_ID_0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case COMMAND_ID_1:
+ if (pXSprite->state == 1) break;
+ case COMMAND_ID_21:
+ SetSpriteState(nSprite, pXSprite, 1);
+
+ // force OFF after *all* TX objects reach the goal value
+ if (pSprite->hitag == 0 && goalValueIsReached(pXSprite)) {
+ evPost(nSprite, 3, 0, COMMAND_ID_0);
+ break;
+ }
+
+ if (pXSprite->data1 > 0 && pXSprite->data1 <= 4 && pXSprite->data2 > 0) {
+ if (pXSprite->txID != 0) {
+ if (pXSprite->command == COMMAND_ID_5)
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ else {
+ evSend(nSprite, 3, pXSprite->txID, kGDXCommandPaste);
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID) pXSprite->command);
+ }
+ }
+ if (pXSprite->busyTime > 0) evPost(nSprite, 3, pXSprite->busyTime, COMMAND_ID_21);
+ }
+ break;
+ case COMMAND_ID_3:
+ if (pXSprite->state == 0) evPost(nSprite, 3, 0, COMMAND_ID_21);
+ else evPost(nSprite, 3, 0, COMMAND_ID_0);
+ break;
+ }
+ break;
+ case 700:
+ case 701:
+ case 702:
+ case 703:
+ case 704:
+ case 705:
+ case 706:
+ case 707:
+ case 708:
+ switch (a3.cmd)
+ {
+ case 0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case 21:
+ if (pSprite->type != 700)
+ ActivateGenerator(nSprite);
+ if (pXSprite->txID)
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
+ if (pXSprite->busyTime > 0)
+ {
+ int nRand = Random2(pXSprite->data1);
+ evPost(nSprite, 3, 120*(nRand+pXSprite->busyTime) / 10, COMMAND_ID_21);
+ }
+ break;
+ default:
+ if (!pXSprite->state)
+ {
+ SetSpriteState(nSprite, pXSprite, 1);
+ evPost(nSprite, 3, 0, COMMAND_ID_21);
+ }
+ break;
+ }
+ break;
+ case 711:
+ if (gGameOptions.nGameType == 0)
+ {
+ if (gMe->pXSprite->health <= 0)
+ break;
+ gMe->at30a = 0;
+ }
+ sndStartSample(pXSprite->data1, -1, 1, 0);
+ break;
+ case 416:
+ case 417:
+ case 425:
+ case 426:
+ case 427:
+ switch (a3.cmd)
+ {
+ case 0:
+ if (SetSpriteState(nSprite, pXSprite, 0))
+ actActivateGibObject(pSprite, pXSprite);
+ break;
+ case 1:
+ if (SetSpriteState(nSprite, pXSprite, 1))
+ actActivateGibObject(pSprite, pXSprite);
+ break;
+ default:
+ if (SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1))
+ actActivateGibObject(pSprite, pXSprite);
+ break;
+ }
+ break;
+ default:
+ switch (a3.cmd)
+ {
+ case 0:
+ SetSpriteState(nSprite, pXSprite, 0);
+ break;
+ case 1:
+ SetSpriteState(nSprite, pXSprite, 1);
+ break;
+ default:
+ SetSpriteState(nSprite, pXSprite, pXSprite->state ^ 1);
+ break;
+ }
+ break;
+ }
+}
+
+// by NoOne: this function stops wind on all TX sectors affected by WindGen after it goes off state.
+void stopWindOnSectors(XSPRITE* pXSource) {
+ for (int i = bucketHead[pXSource->txID]; i < bucketHead[pXSource->txID + 1]; i++) {
+ if (rxBucket[i].type != 3) continue;
+ XSECTOR * pXSector = &xsector[sector[rxBucket[i].index].extra];
+ if ((pXSector->state == 1 && !pXSector->windAlways) || sprite[pXSource->reference].hitag == 1)
+ pXSector->windVel = 0;
+ }
+}
+
+void useSectorWindGen(XSPRITE* pXSource, sectortype* pSector) {
+
+ spritetype* pSource = &sprite[pXSource->reference];
+ XSECTOR* pXSector = NULL; bool forceWind = false;
+
+ if (pSector == NULL) {
+
+ if (sector[pSource->sectnum].extra < 0) {
+ int nXSector = dbInsertXSector(pSource->sectnum);
+ if (nXSector > 0) pXSector = &xsector[nXSector];
+ else return;
+
+ forceWind = true;
+
+ } else {
+ pXSector = &xsector[sector[pSource->sectnum].extra];
+ }
+ } else {
+ pXSector = &xsector[pSector->extra];
+ }
+
+ if (pSource->hitag) {
+ pXSector->panAlways = 1;
+ pXSector->windAlways = 1;
+ } else if (forceWind)
+ pXSector->windAlways = 1;
+
+ if (pXSource->data2 > 32766) pXSource->data2 = 32767;
+
+ if (pXSource->data1 == 1 || pXSource->data1 == 3) pXSector->windVel = Random(pXSource->data2);
+ else pXSector->windVel = pXSource->data2;
+
+ if (pXSource->data1 == 2 || pXSource->data1 == 3) {
+ short ang = pSource->ang;
+ while (pSource->ang == ang)
+ pSource->ang = Random3(kAng360);
+ }
+
+ pXSector->windAng = pSource->ang;
+
+ if (pXSource->data3 > 0 && pXSource->data3 < 4) {
+ switch (pXSource->data3) {
+ case 1:
+ pXSector->panFloor = true;
+ pXSector->panCeiling = false;
+ break;
+ case 2:
+ pXSector->panFloor = false;
+ pXSector->panCeiling = true;
+ break;
+ case 3:
+ pXSector->panFloor = true;
+ pXSector->panCeiling = true;
+ break;
+ }
+
+ pXSector->panAngle = pXSector->windAng;
+ pXSector->panVel = pXSector->windVel;
+ }
+}
+
+void useSeqSpawnerGen(XSPRITE* pXSource, spritetype* pSprite) {
+ if (pSprite == NULL) pSprite = &sprite[pXSource->reference];
+ if (pSprite->extra < 0) return;
+
+ seqSpawn(pXSource->data2, 3, pSprite->extra, (pXSource->data3 > 0) ? pXSource->data3 : -1);
+ if (pXSource->data4 > 0)
+ sfxPlay3DSound(pSprite, pXSource->data4, -1, 0);
+}
+
+void useEffectGen(XSPRITE* pXSource, spritetype* pSprite) {
+ if (pSprite == NULL) pSprite = &sprite[pXSource->reference];
+ if (pSprite->extra < 0) return;
+
+ int top, bottom; GetSpriteExtents(pSprite, &top, &bottom); int cnt = pXSource->data4;
+ spritetype* pEffect = NULL; if (cnt > 32) cnt = 32;
+
+ while (cnt-- >= 0) {
+ if (cnt > 0) {
+
+ int dx = Random3(250);
+ int dy = Random3(150);
+
+ pEffect = gFX.fxSpawn((FX_ID)pXSource->data2, pSprite->sectnum, pSprite->x + dx, pSprite->y + dy, top, 0);
+
+ }
+ else {
+ pEffect = gFX.fxSpawn((FX_ID)pXSource->data2, pSprite->sectnum, pSprite->x, pSprite->y, top, 0);
+ }
+
+ if (pEffect != NULL) {
+ if (pEffect->pal <= 0) pEffect->pal = pSprite->pal;
+ if (pEffect->xrepeat <= 0) pEffect->xrepeat = pSprite->xrepeat;
+ if (pEffect->yrepeat <= 0) pEffect->yrepeat = pSprite->yrepeat;
+ if (pEffect->shade == 0) pEffect->shade = pSprite->shade;
+ }
+ }
+
+ if (pXSource->data3 > 0)
+ sfxPlay3DSound(pSprite, pXSource->data3, -1, 0);
+
+}
+
+
+
+void SetupGibWallState(walltype *pWall, XWALL *pXWall)
+{
+ walltype *pWall2 = NULL;
+ if (pWall->nextwall >= 0)
+ pWall2 = &wall[pWall->nextwall];
+ if (pXWall->state)
+ {
+ pWall->cstat &= ~65;
+ if (pWall2)
+ {
+ pWall2->cstat &= ~65;
+ pWall->cstat &= ~16;
+ pWall2->cstat &= ~16;
+ }
+ return;
+ }
+ char bVector = pXWall->triggerVector != 0;
+ pWall->cstat |= 1;
+ if (bVector)
+ pWall->cstat |= 64;
+ if (pWall2)
+ {
+ pWall2->cstat |= 1;
+ if (bVector)
+ pWall2->cstat |= 64;
+ pWall->cstat |= 16;
+ pWall2->cstat |= 16;
+ }
+}
+
+void OperateWall(int nWall, XWALL *pXWall, EVENT a3)
+{
+ walltype *pWall = &wall[nWall];
+ switch (a3.cmd)
+ {
+ case 6:
+ pXWall->locked = 1;
+ return;
+ case 7:
+ pXWall->locked = 0;
+ return;
+ case 8:
+ pXWall->locked ^= 1;
+ return;
+ }
+ if (pWall->lotag == 511)
+ {
+ char bStatus;
+ switch (a3.cmd)
+ {
+ case 1:
+ case 51:
+ bStatus = SetWallState(nWall, pXWall, 1);
+ break;
+ case 0:
+ bStatus = SetWallState(nWall, pXWall, 0);
+ break;
+ default:
+ bStatus = SetWallState(nWall, pXWall, pXWall->state^1);
+ break;
+ }
+ if (bStatus)
+ {
+ SetupGibWallState(pWall, pXWall);
+ if (pXWall->state)
+ {
+ CGibVelocity vel(100, 100, 250);
+ int nType = ClipRange(pXWall->data, 0, 31);
+ if (nType > 0)
+ GibWall(nWall, (GIBTYPE)nType, &vel);
+ }
+ }
+ return;
+ }
+ switch (a3.cmd)
+ {
+ case 0:
+ SetWallState(nWall, pXWall, 0);
+ break;
+ case 1:
+ SetWallState(nWall, pXWall, 1);
+ break;
+ default:
+ SetWallState(nWall, pXWall, pXWall->state ^ 1);
+ break;
+ }
+}
+
+void SectorStartSound(int nSector, int nState)
+{
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 0 && pSprite->type == 709)
+ {
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (nState)
+ {
+ if (pXSprite->data3)
+ sfxPlay3DSound(pSprite, pXSprite->data3, 0, 0);
+ }
+ else
+ {
+ if (pXSprite->data1)
+ sfxPlay3DSound(pSprite, pXSprite->data1, 0, 0);
+ }
+ }
+ }
+}
+
+void SectorEndSound(int nSector, int nState)
+{
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 0 && pSprite->type == 709)
+ {
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (nState)
+ {
+ if (pXSprite->data2)
+ sfxPlay3DSound(pSprite, pXSprite->data2, 0, 0);
+ }
+ else
+ {
+ if (pXSprite->data4)
+ sfxPlay3DSound(pSprite, pXSprite->data4, 0, 0);
+ }
+ }
+ }
+}
+
+void PathSound(int nSector, int nSound)
+{
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 0 && pSprite->type == 709)
+ sfxPlay3DSound(pSprite, nSound, 0, 0);
+ }
+}
+
+void DragPoint(int nWall, int x, int y)
+{
+ viewInterpolateWall(nWall, &wall[nWall]);
+ wall[nWall].x = x;
+ wall[nWall].y = y;
+
+ int vsi = numwalls;
+ int vb = nWall;
+ do
+ {
+ if (wall[vb].nextwall >= 0)
+ {
+ vb = wall[wall[vb].nextwall].point2;
+ viewInterpolateWall(vb, &wall[vb]);
+ wall[vb].x = x;
+ wall[vb].y = y;
+ }
+ else
+ {
+ vb = nWall;
+ do
+ {
+ if (wall[lastwall(vb)].nextwall >= 0)
+ {
+ vb = wall[lastwall(vb)].nextwall;
+ viewInterpolateWall(vb, &wall[vb]);
+ wall[vb].x = x;
+ wall[vb].y = y;
+ }
+ else
+ break;
+ vsi--;
+ } while (vb != nWall && vsi > 0);
+ break;
+ }
+ vsi--;
+ } while (vb != nWall && vsi > 0);
+}
+
+void TranslateSector(int nSector, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, char a12)
+{
+ int x, y;
+ int nXSector = sector[nSector].extra;
+ XSECTOR *pXSector = &xsector[nXSector];
+ int v20 = interpolate(a6, a9, a2);
+ int vc = interpolate(a6, a9, a3);
+ int v28 = vc - v20;
+ int v24 = interpolate(a7, a10, a2);
+ int v8 = interpolate(a7, a10, a3);
+ int v2c = v8 - v24;
+ int v44 = interpolate(a8, a11, a2);
+ int vbp = interpolate(a8, a11, a3);
+ int v14 = vbp - v44;
+ int nWall = sector[nSector].wallptr;
+ if (a12)
+ {
+ for (int i = 0; i < sector[nSector].wallnum; nWall++, i++)
+ {
+ x = baseWall[nWall].x;
+ y = baseWall[nWall].y;
+ if (vbp)
+ RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
+ DragPoint(nWall, x+vc-a4, y+v8-a5);
+ }
+ }
+ else
+ {
+ for (int i = 0; i < sector[nSector].wallnum; nWall++, i++)
+ {
+ int v10 = wall[nWall].point2;
+ x = baseWall[nWall].x;
+ y = baseWall[nWall].y;
+ if (wall[nWall].cstat&16384)
+ {
+ if (vbp)
+ RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
+ DragPoint(nWall, x+vc-a4, y+v8-a5);
+ if ((wall[v10].cstat&49152) == 0)
+ {
+ x = baseWall[v10].x;
+ y = baseWall[v10].y;
+ if (vbp)
+ RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
+ DragPoint(v10, x+vc-a4, y+v8-a5);
+ }
+ continue;
+ }
+ if (wall[nWall].cstat&32768)
+ {
+ if (vbp)
+ RotatePoint((int*)&x, (int*)&y, -vbp, a4, a5);
+ DragPoint(nWall, x-(vc-a4), y-(v8-a5));
+ if ((wall[v10].cstat&49152) == 0)
+ {
+ x = baseWall[v10].x;
+ y = baseWall[v10].y;
+ if (vbp)
+ RotatePoint((int*)&x, (int*)&y, -vbp, a4, a5);
+ DragPoint(v10, x-(vc-a4), y-(v8-a5));
+ }
+ continue;
+ }
+ }
+ }
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ // By NoOne: allow to move markers by sector movements in game if hitag 1 is added in editor.
+ if (pSprite->statnum == 10 || pSprite->statnum == 16) {
+ if (!(pSprite->hitag&kHitagExtBit)) continue;
+ }
+ x = baseSprite[nSprite].x;
+ y = baseSprite[nSprite].y;
+ if (sprite[nSprite].cstat&8192)
+ {
+ if (vbp)
+ RotatePoint((int*)&x, (int*)&y, vbp, a4, a5);
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->ang = (pSprite->ang+v14)&2047;
+ pSprite->x = x+vc-a4;
+ pSprite->y = y+v8-a5;
+ }
+ else if (sprite[nSprite].cstat&16384)
+ {
+ if (vbp)
+ RotatePoint((int*)& x, (int*)& y, -vbp, a4, a4);
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->ang = (pSprite->ang-v14)&2047;
+ pSprite->x = x-(vc-a4);
+ pSprite->y = y-(v8-a5);
+ }
+ else if (pXSector->Drag)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ int floorZ = getflorzofslope(nSector, pSprite->x, pSprite->y);
+ if (!(pSprite->cstat&48) && floorZ <= bottom)
+ {
+ if (v14)
+ RotatePoint((int*)&pSprite->x, (int*)&pSprite->y, v14, v20, v24);
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->ang = (pSprite->ang+v14)&2047;
+ pSprite->x += v28;
+ pSprite->y += v2c;
+ }
+ }
+ }
+}
+
+void ZTranslateSector(int nSector, XSECTOR *pXSector, int a3, int a4)
+{
+ sectortype *pSector = §or[nSector];
+ viewInterpolateSector(nSector, pSector);
+ int dz = pXSector->at28_0-pXSector->at24_0;
+ if (dz != 0)
+ {
+ int oldZ = pSector->floorz;
+ baseFloor[nSector] = pSector->floorz = pXSector->at24_0 + mulscale16(dz, GetWaveValue(a3, a4));
+ velFloor[nSector] += (pSector->floorz-oldZ)<<8;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 10 || pSprite->statnum == 16)
+ continue;
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (pSprite->cstat&8192)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z += pSector->floorz-oldZ;
+ }
+ else if (pSprite->hitag&2)
+ pSprite->hitag |= 4;
+ else if (oldZ <= bottom && !(pSprite->cstat&48))
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z += pSector->floorz-oldZ;
+ }
+ }
+ }
+ dz = pXSector->at20_0-pXSector->at1c_0;
+ if (dz != 0)
+ {
+ int oldZ = pSector->ceilingz;
+ baseCeil[nSector] = pSector->ceilingz = pXSector->at1c_0 + mulscale16(dz, GetWaveValue(a3, a4));
+ velCeil[nSector] += (pSector->ceilingz-oldZ)<<8;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 10 || pSprite->statnum == 16)
+ continue;
+ if (pSprite->cstat&16384)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z += pSector->ceilingz-oldZ;
+ }
+ }
+ }
+}
+
+int GetHighestSprite(int nSector, int nStatus, int *a3)
+{
+ *a3 = sector[nSector].floorz;
+ int v8 = -1;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ if (sprite[nSprite].statnum == nStatus || nStatus == 1024)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (top-pSprite->z > *a3)
+ {
+ *a3 = top-pSprite->z;
+ v8 = nSprite;
+ }
+ }
+ }
+ return v8;
+}
+
+int GetCrushedSpriteExtents(unsigned int nSector, int *pzTop, int *pzBot)
+{
+ dassert(pzTop != NULL && pzBot != NULL);
+ dassert(nSector < (unsigned int)numsectors);
+ int vc = -1;
+ sectortype *pSector = §or[nSector];
+ int vbp = pSector->ceilingz;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 6 || pSprite->statnum == 4)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (vbp > top)
+ {
+ vbp = top;
+ *pzTop = top;
+ *pzBot = bottom;
+ vc = nSprite;
+ }
+ }
+ }
+ return vc;
+}
+
+int VCrushBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ int nXSector = sector[nSector].extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ int nWave;
+ if (pXSector->busy < a2)
+ nWave = pXSector->at7_2;
+ else
+ nWave = pXSector->at7_5;
+ int dz1 = pXSector->at20_0 - pXSector->at1c_0;
+ int vc = pXSector->at1c_0;
+ if (dz1 != 0)
+ vc += mulscale16(dz1, GetWaveValue(a2, nWave));
+ int dz2 = pXSector->at28_0 - pXSector->at24_0;
+ int v10 = pXSector->at24_0;
+ if (dz2 != 0)
+ v10 += mulscale16(dz2, GetWaveValue(a2, nWave));
+ int v18;
+ if (GetHighestSprite(nSector, 6, &v18) >= 0 && vc >= v18)
+ return 1;
+ viewInterpolateSector(nSector, §or[nSector]);
+ if (dz1 != 0)
+ sector[nSector].ceilingz = vc;
+ if (dz2 != 0)
+ sector[nSector].floorz = v10;
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ return 3;
+ }
+ return 0;
+}
+
+int VSpriteBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ int nXSector = sector[nSector].extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ int nWave;
+ if (pXSector->busy < a2)
+ nWave = pXSector->at7_2;
+ else
+ nWave = pXSector->at7_5;
+ int dz1 = pXSector->at28_0 - pXSector->at24_0;
+ if (dz1 != 0)
+ {
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->cstat&8192)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z = baseSprite[nSprite].z+mulscale16(dz1, GetWaveValue(a2, nWave));
+ }
+ }
+ }
+ int dz2 = pXSector->at20_0 - pXSector->at1c_0;
+ if (dz2 != 0)
+ {
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->cstat&16384)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z = baseSprite[nSprite].z+mulscale16(dz2, GetWaveValue(a2, nWave));
+ }
+ }
+ }
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ return 3;
+ }
+ return 0;
+}
+
+int VDoorBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ int nXSector = sector[nSector].extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ int vbp;
+ if (pXSector->state)
+ vbp = 65536/ClipLow((120*pXSector->busyTimeA)/10, 1);
+ else
+ vbp = -65536/ClipLow((120*pXSector->busyTimeB)/10, 1);
+ int top, bottom;
+ int nSprite = GetCrushedSpriteExtents(nSector,&top,&bottom);
+ if (nSprite >= 0 && a2 > pXSector->busy)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if (pXSector->at20_0 > pXSector->at1c_0 || pXSector->at28_0 < pXSector->at24_0)
+ {
+ if (pXSector->interruptable)
+ {
+ if (pXSector->Crush)
+ {
+ if (pXSprite->health <= 0)
+ return 2;
+ int nDamage;
+ if (pXSector->data == 0)
+ nDamage = 500;
+ else
+ nDamage = pXSector->data;
+ actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
+ }
+ a2 = ClipRange(a2-(vbp/2)*4, 0, 65536);
+ }
+ else if (pXSector->Crush && pXSprite->health > 0)
+ {
+ int nDamage;
+ if (pXSector->data == 0)
+ nDamage = 500;
+ else
+ nDamage = pXSector->data;
+ actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
+ a2 = ClipRange(a2-(vbp/2)*4, 0, 65536);
+ }
+ }
+ }
+ else if (nSprite >= 0 && a2 < pXSector->busy)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ dassert(pSprite->extra > 0 && pSprite->extra < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if (pXSector->at1c_0 > pXSector->at20_0 || pXSector->at24_0 < pXSector->at28_0)
+ {
+ if (pXSector->interruptable)
+ {
+ if (pXSector->Crush)
+ {
+ if (pXSprite->health <= 0)
+ return 2;
+ int nDamage;
+ if (pXSector->data == 0)
+ nDamage = 500;
+ else
+ nDamage = pXSector->data;
+ actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
+ }
+ a2 = ClipRange(a2+(vbp/2)*4, 0, 65536);
+ }
+ else if (pXSector->Crush && pXSprite->health > 0)
+ {
+ int nDamage;
+ if (pXSector->data == 0)
+ nDamage = 500;
+ else
+ nDamage = pXSector->data;
+ actDamageSprite(nSprite, &sprite[nSprite], DAMAGE_TYPE_0, nDamage<<4);
+ a2 = ClipRange(a2+(vbp/2)*4, 0, 65536);
+ }
+ }
+ }
+ int nWave;
+ if (pXSector->busy < a2)
+ nWave = pXSector->at7_2;
+ else
+ nWave = pXSector->at7_5;
+ ZTranslateSector(nSector, pXSector, a2, nWave);
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ return 3;
+ }
+ return 0;
+}
+
+int HDoorBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ int nWave;
+ if (pXSector->busy < a2)
+ nWave = pXSector->at7_2;
+ else
+ nWave = pXSector->at7_5;
+ spritetype *pSprite1 = &sprite[pXSector->at2c_0];
+ spritetype *pSprite2 = &sprite[pXSector->at2e_0];
+ TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->lotag == 616);
+ ZTranslateSector(nSector, pXSector, a2, nWave);
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ return 3;
+ }
+ return 0;
+}
+
+int RDoorBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ int nWave;
+ if (pXSector->busy < a2)
+ nWave = pXSector->at7_2;
+ else
+ nWave = pXSector->at7_5;
+ spritetype *pSprite = &sprite[pXSector->at2c_0];
+ TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, 0, pSprite->x, pSprite->y, pSprite->ang, pSector->lotag == 617);
+ ZTranslateSector(nSector, pXSector, a2, nWave);
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ return 3;
+ }
+ return 0;
+}
+
+int StepRotateBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ spritetype *pSprite = &sprite[pXSector->at2c_0];
+ int vbp;
+ if (pXSector->busy < a2)
+ {
+ vbp = pXSector->data+pSprite->ang;
+ int nWave = pXSector->at7_2;
+ TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, pXSector->data, pSprite->x, pSprite->y, vbp, 1);
+ }
+ else
+ {
+ vbp = pXSector->data-pSprite->ang;
+ int nWave = pXSector->at7_5;
+ TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite->x, pSprite->y, vbp, pSprite->x, pSprite->y, pXSector->data, 1);
+ }
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ pXSector->data = vbp&2047;
+ return 3;
+ }
+ return 0;
+}
+
+int GenSectorBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ pXSector->busy = a2;
+ if (pXSector->command == 5 && pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, COMMAND_ID_5);
+ if ((a2&0xffff) == 0)
+ {
+ SetSectorState(nSector, pXSector, a2>>16);
+ SectorEndSound(nSector, a2>>16);
+ return 3;
+ }
+ return 0;
+}
+
+int PathBusy(unsigned int nSector, unsigned int a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ spritetype *pSprite = &sprite[basePath[nSector]];
+ spritetype *pSprite1 = &sprite[pXSector->at2c_0];
+ XSPRITE *pXSprite1 = &xsprite[pSprite1->extra];
+ spritetype *pSprite2 = &sprite[pXSector->at2e_0];
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ int nWave = pXSprite1->wave;
+ TranslateSector(nSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), pSprite->x, pSprite->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, 1);
+ ZTranslateSector(nSector, pXSector, a2, nWave);
+ pXSector->busy = a2;
+ if ((a2&0xffff) == 0)
+ {
+ evPost(nSector, 6, (120*pXSprite2->waitTime)/10, COMMAND_ID_1);
+ pXSector->state = 0;
+ pXSector->busy = 0;
+ if (pXSprite1->data4)
+ PathSound(nSector, pXSprite1->data4);
+ pXSector->at2c_0 = pXSector->at2e_0;
+ pXSector->data = pXSprite2->data1;
+ return 3;
+ }
+ return 0;
+}
+
+void OperateDoor(unsigned int nSector, XSECTOR *pXSector, EVENT a3, BUSYID a4)
+{
+ switch (a3.cmd)
+ {
+ case 0:
+ if (pXSector->busy)
+ {
+ AddBusy(nSector, a4, -65536/ClipLow((pXSector->busyTimeB*120)/10, 1));
+ SectorStartSound(nSector, 1);
+ }
+ break;
+ case 1:
+ if (pXSector->busy != 0x10000)
+ {
+ AddBusy(nSector, a4, 65536/ClipLow((pXSector->busyTimeA*120)/10, 1));
+ SectorStartSound(nSector, 0);
+ }
+ break;
+ default:
+ if (pXSector->busy&0xffff)
+ {
+ if (pXSector->interruptable)
+ {
+ ReverseBusy(nSector, a4);
+ pXSector->state = !pXSector->state;
+ }
+ }
+ else
+ {
+ char t = !pXSector->state;
+ int nDelta;
+ if (t)
+ nDelta = 65536/ClipLow((pXSector->busyTimeA*120)/10, 1);
+ else
+ nDelta = -65536/ClipLow((pXSector->busyTimeB*120)/10, 1);
+ AddBusy(nSector, a4, nDelta);
+ SectorStartSound(nSector, pXSector->state);
+ }
+ break;
+ }
+}
+
+char SectorContainsDudes(int nSector)
+{
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ if (sprite[nSprite].statnum == 6)
+ return 1;
+ }
+ return 0;
+}
+
+void TeleFrag(int nKiller, int nSector)
+{
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 6)
+ actDamageSprite(nKiller, pSprite, DAMAGE_TYPE_3, 4000);
+ else if (pSprite->statnum == 4)
+ actDamageSprite(nKiller, pSprite, DAMAGE_TYPE_3, 4000);
+ }
+}
+
+void OperateTeleport(unsigned int nSector, XSECTOR *pXSector)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ int nDest = pXSector->at2c_0;
+ dassert(nDest < kMaxSprites);
+ spritetype *pDest = &sprite[nDest];
+ dassert(pDest->statnum == kStatMarker);
+ dassert(pDest->type == kMarkerWarpDest);
+ dassert(pDest->sectnum >= 0 && pDest->sectnum < kMaxSectors);
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->statnum == 6)
+ {
+ PLAYER *pPlayer;
+ char bPlayer = IsPlayerSprite(pSprite);
+ if (bPlayer)
+ pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ else
+ pPlayer = NULL;
+ if (bPlayer || !SectorContainsDudes(pDest->sectnum))
+ {
+ if (!(gGameOptions.uNetGameFlags&2))
+ TeleFrag(pXSector->data, pDest->sectnum);
+ pSprite->x = pDest->x;
+ pSprite->y = pDest->y;
+ pSprite->z += sector[pDest->sectnum].floorz-sector[nSector].floorz;
+ pSprite->ang = pDest->ang;
+ ChangeSpriteSect(nSprite, pDest->sectnum);
+ sfxPlay3DSound(pDest, 201, -1, 0);
+ xvel[nSprite] = yvel[nSprite] = zvel[nSprite] = 0;
+ ClearBitString(gInterpolateSprite, nSprite);
+ viewBackupSpriteLoc(nSprite, pSprite);
+ if (pPlayer)
+ {
+ playerResetInertia(pPlayer);
+ pPlayer->at6b = pPlayer->at73 = 0;
+ }
+ }
+ }
+ }
+}
+
+void OperatePath(unsigned int nSector, XSECTOR *pXSector, EVENT a3)
+{
+ int nSprite;
+ spritetype *pSprite = NULL;
+ XSPRITE *pXSprite;
+ dassert(nSector < (unsigned int)numsectors);
+ spritetype *pSprite2 = &sprite[pXSector->at2c_0];
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+ int nId = pXSprite2->data2;
+ for (nSprite = headspritestat[16]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ pSprite = &sprite[nSprite];
+ if (pSprite->type == 15)
+ {
+ pXSprite = &xsprite[pSprite->extra];
+ if (pXSprite->data1 == nId)
+ break;
+ }
+ }
+ if (nSprite < 0)
+ ThrowError("Unable to find path marker with id #%d", nId);
+ pXSector->at2e_0 = nSprite;
+ pXSector->at24_0 = pSprite2->z;
+ pXSector->at28_0 = pSprite->z;
+ switch (a3.cmd)
+ {
+ case 1:
+ pXSector->state = 0;
+ pXSector->busy = 0;
+ AddBusy(nSector, BUSYID_7, 65536/ClipLow((120*pXSprite2->busyTime)/10,1));
+ if (pXSprite2->data3)
+ PathSound(nSector, pXSprite2->data3);
+ break;
+ }
+}
+
+void OperateSector(unsigned int nSector, XSECTOR *pXSector, EVENT a3)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ sectortype *pSector = §or[nSector];
+ switch (a3.cmd)
+ {
+ case 6:
+ pXSector->locked = 1;
+ break;
+ case 7:
+ pXSector->locked = 0;
+ // By NoOne: reset counter sector state and make it work again after unlock, so it can be used again.
+ // See callback.cpp for more info.
+ if (pSector->lotag == kSecCounter) {
+ pXSector->state = 0;
+ evPost(nSector, 6, 0, CALLBACK_ID_12);
+ }
+ break;
+ case 8:
+ pXSector->locked ^= 1;
+ // same as above...
+ if (pSector->lotag == kSecCounter && pXSector->locked != 1) {
+ pXSector->state = 0;
+ evPost(nSector, 6, 0, CALLBACK_ID_12);
+ }
+ break;
+ case 9:
+ pXSector->at1b_2 = 0;
+ pXSector->at1b_3 = 1;
+ break;
+ case 10:
+ pXSector->at1b_2 = 1;
+ pXSector->at1b_3 = 0;
+ break;
+ case 11:
+ pXSector->at1b_2 = 1;
+ pXSector->at1b_3 = 1;
+ break;
+ default:
+ switch (pSector->lotag)
+ {
+ case 602:
+ OperateDoor(nSector, pXSector, a3, BUSYID_1);
+ break;
+ case 600:
+ OperateDoor(nSector, pXSector, a3, BUSYID_2);
+ break;
+ case 614:
+ case 616:
+ OperateDoor(nSector, pXSector, a3, BUSYID_3);
+ break;
+ case 615:
+ case 617:
+ OperateDoor(nSector, pXSector, a3, BUSYID_4);
+ break;
+ case 613:
+ switch (a3.cmd)
+ {
+ case 1:
+ pXSector->state = 0;
+ pXSector->busy = 0;
+ AddBusy(nSector, BUSYID_5, 65536/ClipLow((120*pXSector->busyTimeA)/10, 1));
+ SectorStartSound(nSector, 0);
+ break;
+ case 0:
+ pXSector->state = 1;
+ pXSector->busy = 65536;
+ AddBusy(nSector, BUSYID_5, -65536/ClipLow((120*pXSector->busyTimeB)/10, 1));
+ SectorStartSound(nSector, 1);
+ break;
+ }
+ break;
+ case 604:
+ OperateTeleport(nSector, pXSector);
+ break;
+ case 612:
+ OperatePath(nSector, pXSector, a3);
+ break;
+ default:
+ if (pXSector->busyTimeA || pXSector->busyTimeB)
+ OperateDoor(nSector, pXSector, a3, BUSYID_6);
+ else
+ {
+ switch (a3.cmd)
+ {
+ case 0:
+ SetSectorState(nSector, pXSector, 0);
+ break;
+ case 1:
+ SetSectorState(nSector, pXSector, 1);
+ break;
+ default:
+ SetSectorState(nSector, pXSector, pXSector->state^1);
+ break;
+ }
+ }
+ break;
+ }
+ break;
+ }
+}
+
+void InitPath(unsigned int nSector, XSECTOR *pXSector)
+{
+ int nSprite;
+ spritetype *pSprite;
+ XSPRITE *pXSprite;
+ dassert(nSector < (unsigned int)numsectors);
+ int nId = pXSector->data;
+ for (nSprite = headspritestat[16]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ pSprite = &sprite[nSprite];
+ if (pSprite->type == 15)
+ {
+ pXSprite = &xsprite[pSprite->extra];
+ if (pXSprite->data1 == nId)
+ break;
+ }
+ }
+ if (nSprite < 0)
+ ThrowError("Unable to find path marker with id #%d", nId);
+ pXSector->at2c_0 = nSprite;
+ basePath[nSector] = nSprite;
+ if (pXSector->state)
+ evPost(nSector, 6, 0, COMMAND_ID_1);
+}
+
+void LinkSector(int nSector, XSECTOR *pXSector, EVENT a3)
+{
+ sectortype *pSector = §or[nSector];
+ int nBusy = GetSourceBusy(a3);
+ switch (pSector->lotag)
+ {
+ case 602:
+ VSpriteBusy(nSector, nBusy);
+ break;
+ case 600:
+ VDoorBusy(nSector, nBusy);
+ break;
+ case 614:
+ case 616:
+ HDoorBusy(nSector, nBusy);
+ break;
+ case 615:
+ case 617:
+ RDoorBusy(nSector, nBusy);
+ break;
+ /* By NoOne: add link support for counter sectors so they can change necessary type and count of types*/
+ case kSecCounter:
+ {
+ int nXIndex;
+ nXIndex = sector[a3.index].extra;
+ XSECTOR* pXSector2 = &xsector[nXIndex];
+ pXSector->waitTimeA = pXSector2->waitTimeA;
+ pXSector->data = pXSector2->data;
+ break;
+ }
+ default:
+ pXSector->busy = nBusy;
+ if ((pXSector->busy&0xffff) == 0)
+ SetSectorState(nSector, pXSector, nBusy>>16);
+ break;
+ }
+}
+
+void LinkSprite(int nSprite, XSPRITE *pXSprite, EVENT a3)
+{
+ spritetype *pSprite = &sprite[nSprite];
+ int nBusy = GetSourceBusy(a3);
+ switch (pSprite->type)
+ {
+
+ //By NoOne: these can be linked too now, so it's possible to change palette, underwater status and more...
+ case kMarkerLowWater:
+ case kMarkerUpWater:
+ case kMarkerUpGoo:
+ case kMarkerLowGoo:
+ case kMarkerUpLink:
+ case kMarkerLowLink:
+ case kMarkerUpStack:
+ case kMarkerLowStack:
+ {
+ if (a3.type != 3) break;
+ spritetype *pSprite2 = &sprite[a3.index];
+ if (pSprite2->extra < 0) break;
+ XSPRITE *pXSprite2 = &xsprite[pSprite2->extra];
+
+ // Only lower to lower and upper to upper linking allowed.
+ switch (pSprite->type) {
+ case kMarkerLowWater:
+ case kMarkerLowLink:
+ case kMarkerLowStack:
+ case kMarkerLowGoo:
+ switch (pSprite2->type) {
+ case kMarkerLowWater:
+ case kMarkerLowLink:
+ case kMarkerLowStack:
+ case kMarkerLowGoo:
+ break;
+ default:
+ return;
+ }
+ break;
+
+ case kMarkerUpWater:
+ case kMarkerUpLink:
+ case kMarkerUpStack:
+ case kMarkerUpGoo:
+ switch (pSprite2->type) {
+ case kMarkerUpWater:
+ case kMarkerUpLink:
+ case kMarkerUpStack:
+ case kMarkerUpGoo:
+ break;
+ default:
+ return;
+ }
+ break;
+ }
+
+ // swap link location
+ /*short tmp1 = pXSprite2.data1;*/
+ /*pXSprite2.data1 = pXSprite.data1;*/
+ /*pXSprite.data1 = tmp1;*/
+
+ if (pXSprite->data2 < kMaxPAL && pXSprite2->data2 < kMaxPAL)
+ {
+ // swap medium
+ int tmp2 = pXSprite2->data2;
+ pXSprite2->data2 = pXSprite->data2;
+ pXSprite->data2 = tmp2;
+ }
+
+
+ // swap link type // swap link owners (sectors)
+ short tmp3 = pSprite2->type; //short tmp7 = pSprite2.owner;
+ pSprite2->type = pSprite->type; //pSprite2.owner = pSprite.owner;
+ pSprite->type = tmp3; //pSprite.owner = tmp7;
+
+ // Deal with linked sectors
+ sectortype *pSector = §or[pSprite->sectnum];
+ sectortype *pSector2 = §or[pSprite2->sectnum];
+
+ // Check for underwater
+ XSECTOR *pXSector = NULL; XSECTOR *pXSector2 = NULL;
+ if (pSector->extra > 0) pXSector = &xsector[pSector->extra];
+ if (pSector2->extra > 0) pXSector2 = &xsector[pSector2->extra];
+ if (pXSector != NULL && pXSector2 != NULL) {
+ bool tmp6 = pXSector->Underwater;
+ pXSector->Underwater = pXSector2->Underwater;
+ pXSector2->Underwater = tmp6;
+ }
+
+ // optionally swap floorpic
+ if (pXSprite2->data3 == 1) {
+ short tmp4 = pSector->floorpicnum;
+ pSector->floorpicnum = pSector2->floorpicnum;
+ pSector2->floorpicnum = tmp4;
+ }
+
+ // optionally swap ceilpic
+ if (pXSprite2->data4 == 1) {
+ short tmp5 = pSector->ceilingpicnum;
+ pSector->ceilingpicnum = pSector2->ceilingpicnum;
+ pSector2->ceilingpicnum = tmp5;
+ }
+ }
+ break;
+ // By NoOne: add a way to link between path markers, so path sectors can change their path on the fly.
+ case kMarkerPath:
+ {
+ // only path marker to path marker link allowed
+ if (a3.type == 3)
+ {
+ int nXSprite2 = sprite[a3.index].extra;
+ // get master path marker data fields
+ pXSprite->data1 = xsprite[nXSprite2].data1;
+ pXSprite->data2 = xsprite[nXSprite2].data2;
+ pXSprite->data3 = xsprite[nXSprite2].data3; // include soundId(?)
+
+ // get master path marker busy and wait times
+ pXSprite->busyTime = xsprite[nXSprite2].busyTime;
+ pXSprite->waitTime = xsprite[nXSprite2].waitTime;
+
+ }
+ }
+ break;
+ case kSwitchCombo:
+ {
+ if (a3.type == 3)
+ {
+ int nSprite2 = a3.index;
+ int nXSprite2 = sprite[nSprite2].extra;
+ dassert(nXSprite2 > 0 && nXSprite2 < kMaxXSprites);
+ pXSprite->data1 = xsprite[nXSprite2].data1;
+ if (pXSprite->data1 == pXSprite->data2)
+ SetSpriteState(nSprite, pXSprite, 1);
+ else
+ SetSpriteState(nSprite, pXSprite, 0);
+ }
+ }
+ break;
+ default:
+ {
+ pXSprite->busy = nBusy;
+ if ((pXSprite->busy & 0xffff) == 0)
+ SetSpriteState(nSprite, pXSprite, nBusy >> 16);
+ }
+ break;
+ }
+}
+
+void LinkWall(int nWall, XWALL *pXWall, EVENT a3)
+{
+ int nBusy = GetSourceBusy(a3);
+ pXWall->busy = nBusy;
+ if ((pXWall->busy & 0xffff) == 0)
+ SetWallState(nWall, pXWall, nBusy>>16);
+}
+
+void trTriggerSector(unsigned int nSector, XSECTOR *pXSector, int a3)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ if (!pXSector->locked && !pXSector->at16_6)
+ {
+ if (pXSector->triggerOnce)
+ pXSector->at16_6 = 1;
+ if (pXSector->decoupled)
+ {
+ if (pXSector->txID)
+ evSend(nSector, 6, pXSector->txID, (COMMAND_ID)pXSector->command);
+ }
+ else
+ {
+ EVENT evnt;
+ evnt.cmd = a3;
+ OperateSector(nSector, pXSector, evnt);
+ }
+ }
+}
+
+void trMessageSector(unsigned int nSector, EVENT a2)
+{
+ dassert(nSector < (unsigned int)numsectors);
+ dassert(sector[nSector].extra > 0 && sector[nSector].extra < kMaxXSectors);
+ int nXSector = sector[nSector].extra;
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (!pXSector->locked || a2.cmd == 7 || a2.cmd == 8)
+ {
+ if (a2.cmd == 5)
+ LinkSector(nSector, pXSector, a2);
+ else if (a2.cmd == kGDXCommandPaste)
+ pastePropertiesInObj(6, nSector, a2);
+ else
+ OperateSector(nSector, pXSector, a2);
+ }
+}
+
+void trTriggerWall(unsigned int nWall, XWALL *pXWall, int a3)
+{
+ dassert(nWall < (unsigned int)numwalls);
+ if (!pXWall->locked && !pXWall->isTriggered)
+ {
+ if (pXWall->triggerOnce)
+ pXWall->isTriggered = 1;
+ if (pXWall->decoupled)
+ {
+ if (pXWall->txID)
+ evSend(nWall, 0, pXWall->txID, (COMMAND_ID)pXWall->command);
+ }
+ else
+ {
+ EVENT evnt;
+ evnt.cmd = a3;
+ OperateWall(nWall, pXWall, evnt);
+ }
+ }
+}
+
+void trMessageWall(unsigned int nWall, EVENT a2)
+{
+ dassert(nWall < (unsigned int)numwalls);
+ dassert(wall[nWall].extra > 0 && wall[nWall].extra < kMaxXWalls);
+ int nXWall = wall[nWall].extra;
+ XWALL *pXWall = &xwall[nXWall];
+ if (!pXWall->locked || a2.cmd == 7 || a2.cmd == 8)
+ {
+ if (a2.cmd == 5)
+ LinkWall(nWall, pXWall, a2);
+ else if (a2.cmd == kGDXCommandPaste)
+ pastePropertiesInObj(0, nWall, a2);
+ else
+ OperateWall(nWall, pXWall, a2);
+ }
+}
+
+void trTriggerSprite(unsigned int nSprite, XSPRITE *pXSprite, int a3)
+{
+ if (!pXSprite->locked && !pXSprite->isTriggered)
+ {
+ if (pXSprite->triggerOnce)
+ pXSprite->isTriggered = 1;
+ if (pXSprite->Decoupled)
+ {
+ if (pXSprite->txID)
+ evSend(nSprite, 3, pXSprite->txID, (COMMAND_ID)pXSprite->command);
+ }
+ else
+ {
+ EVENT evnt;
+ evnt.cmd = a3;
+ OperateSprite(nSprite, pXSprite, evnt);
+ }
+ }
+}
+
+void trMessageSprite(unsigned int nSprite, EVENT a2)
+{
+ if (sprite[nSprite].statnum == kStatFree)
+ return;
+ int nXSprite = sprite[nSprite].extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (!pXSprite->locked || a2.cmd == 7 || a2.cmd == 8)
+ {
+ if (a2.cmd == 5)
+ LinkSprite(nSprite, pXSprite, a2);
+ else if (a2.cmd == kGDXCommandPaste)
+ pastePropertiesInObj(3, nSprite, a2);
+ else if (a2.cmd == kGDXCommandSpriteDamage)
+ trDamageSprite(3, nSprite, a2);
+ else
+ OperateSprite(nSprite, pXSprite, a2);
+ }
+}
+
+// By NoOne: this function damages sprite
+void trDamageSprite(int type, int nDest, EVENT event) {
+ UNREFERENCED_PARAMETER(type);
+
+ /* - damages xsprite via TX ID - */
+ /* - data3 = damage type - */
+ /* - data4 = damage amount - */
+
+ if (event.type == 3) {
+ spritetype* pSource = NULL; pSource = &sprite[event.index];
+ XSPRITE* pXSource = &xsprite[pSource->extra];
+ if (xsprite[sprite[nDest].extra].health > 0) {
+ if (pXSource->data4 == 0)
+ pXSource->data4 = 65535;
+
+ int dmgType = pXSource->data3;
+ if (pXSource->data3 >= 7)
+ dmgType = Random(6);
+
+ actDamageSprite(pSource->xvel, &sprite[nDest], (DAMAGE_TYPE) dmgType, pXSource->data4);
+ }
+ }
+}
+
+bool valueIsBetween(int val, int min, int max) {
+ return (val > min && val < max);
+}
+// By NoOne: this function used by various new GDX types.
+void pastePropertiesInObj(int type, int nDest, EVENT event) {
+ spritetype* pSource = NULL; pSource = &sprite[event.index];
+ if (pSource == NULL || event.type != 3) return;
+ XSPRITE* pXSource = &xsprite[pSource->extra];
+
+ if (pSource->type == kGDXEffectSpawner) {
+ /* - Effect Spawner can spawn any effect passed in data2 on it's or txID sprite - */
+ if (pXSource->data2 < 0 || pXSource->data2 >= kFXMax) return;
+ else if (type == 3) useEffectGen(pXSource, &sprite[nDest]);
+ return;
+
+ } else if (pSource->type == kGDXSeqSpawner) {
+ /* - SEQ Spawner takes data2 as SEQ ID and spawns it on it's or TX ID sprite - */
+ if (pXSource->data2 <= 0 || !gSysRes.Lookup(pXSource->data2, "SEQ")) return;
+ else if (type == 3) useSeqSpawnerGen(pXSource, &sprite[nDest]);
+ return;
+
+ } else if (pSource->type == kGDXObjDataAccumulator) {
+ /* - Object Data Accumulator allows to perform sum and sub operations in data fields of object - */
+ /* - data1 = destination data index - */
+ /* - data2 = step value - */
+ /* - data3 = min value - */
+ /* - data4 = max value - */
+ /* - min > max = sub, min < max = sum - */
+
+ /* - hitag: 0 = force OFF if goal value was reached for all objects - */
+ /* - hitag: 2 = force swap min and max if goal value was reached - */
+ /* - hitag: 3 = force reset counter - */
+
+ if (pXSource->data3 < 0) pXSource->data3 = 0;
+ else if (pXSource->data3 > 32766) pXSource->data3 = 32767;
+ if (pXSource->data4 < 0) pXSource->data4 = 0;
+ else if (pXSource->data4 > 32766) pXSource->data4 = 32767;
+
+ long data = getDataFieldOfObject(type, nDest, pXSource->data1);
+ if (data == -65535) return;
+ else if (pXSource->data3 < pXSource->data4) {
+
+ if (data < pXSource->data3) data = pXSource->data3;
+ if (data > pXSource->data4) data = pXSource->data4;
+
+ if ((data += pXSource->data2) >= pXSource->data4) {
+ switch (pSource->hitag) {
+ case 0:
+ case 1:
+ if (data > pXSource->data4) data = pXSource->data4;
+ break;
+ case 2:
+ {
+
+ if (data > pXSource->data4) data = pXSource->data4;
+ if (!goalValueIsReached(pXSource)) break;
+ int tmp = pXSource->data4;
+ pXSource->data4 = pXSource->data3;
+ pXSource->data3 = tmp;
+
+ }
+ break;
+ case 3:
+ if (data > pXSource->data4) data = pXSource->data3;
+ break;
+ }
+ }
+
+ }
+ else if (pXSource->data3 > pXSource->data4) {
+
+ if (data > pXSource->data3) data = pXSource->data3;
+ if (data < pXSource->data4) data = pXSource->data4;
+
+ if ((data -= pXSource->data2) <= pXSource->data4) {
+ switch (pSource->hitag) {
+ case 0:
+ case 1:
+ if (data < pXSource->data4) data = pXSource->data4;
+ break;
+ case 2:
+ {
+ if (data < pXSource->data4) data = pXSource->data4;
+ int tmp = pXSource->data4;
+ pXSource->data4 = pXSource->data3;
+ pXSource->data3 = tmp;
+ break;
+ }
+ case 3:
+ if (data < pXSource->data4) data = pXSource->data3;
+ break;
+ }
+ }
+ }
+
+ setDataValueOfObject(type, nDest, pXSource->data1, data);
+ return;
+
+ } else if (pSource->type == kGDXWindGenerator) {
+
+ /* - Wind generator via TX or for current sector if TX ID not specified - */
+ /* - sprite.ang = sector wind direction - */
+ /* - data1 = randomness settings - */
+ /* - 0: no randomness - */
+ /* - 1: randomize wind velocity in data2 - */
+ /* - 2: randomize current generator sprite angle - */
+ /* - 3: randomize both wind velocity and sprite angle - */
+ /* - data2 = wind velocity - */
+ /* - data3 = enable panning according current wind speed and direction - */
+ /* - data4 = pan floor and ceiling settings - */
+ /* - 0: use sector pan settings - */
+ /* - 1: pan only floor - */
+ /* - 2: pan only ceiling - */
+ /* - 3: pan both - */
+
+ /* - hi-tag = 1: force windAlways and panAlways - */
+
+ if (pXSource->data2 < 0) return;
+ else if (type == 6) useSectorWindGen(pXSource, §or[nDest]);
+ return;
+
+
+ } else if (pSource->type == kGDXObjDataChanger) {
+
+ /* - Data field changer via TX - */
+ /* - data1 = sprite data1 / sector data / wall data - */
+ /* - data2 = sprite data2 - */
+ /* - data3 = sprite data3 - */
+ /* - data4 = sprite data4 - */
+
+ switch (type) {
+ // for sectors
+ case 6:
+ {
+ XSECTOR* pXSector = &xsector[sector[nDest].extra];
+
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ pXSector->data = pXSource->data1;
+
+ break;
+ }
+ // for sprites
+ case 3:
+ {
+ XSPRITE* pXSprite = &xsprite[sprite[nDest].extra];
+
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ pXSprite->data1 = pXSource->data1;
+
+ if (valueIsBetween(pXSource->data2, -1, 32767))
+ pXSprite->data2 = pXSource->data2;
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ pXSprite->data3 = pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535))
+ pXSprite->data4 = pXSource->data4;
+
+ break;
+ }
+ // for walls
+ case 0:
+ {
+ XWALL* pXWall = &xwall[wall[nDest].extra];
+
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ pXWall->data = pXSource->data1;
+
+ break;
+ }
+ }
+
+ } else if (pSource->type == kGDXSectorFXChanger) {
+
+ /* - FX Wave changer for sector via TX - */
+ /* - data1 = Wave - */
+ /* - data2 = Amplitude - */
+ /* - data3 = Freq - */
+ /* - data4 = Phase - */
+
+ if (type == 6) {
+ XSECTOR* pXSector = &xsector[sector[nDest].extra];
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ pXSector->wave = pXSource->data1;
+
+ if (pXSource->data2 >= 0) {
+
+ if (pXSource->data2 > 127) pXSector->amplitude = 127;
+ else pXSector->amplitude = pXSource->data2;
+
+ }
+ else if (pXSource->data2 < -1) {
+
+ if (pXSource->data2 < -127) pXSector->amplitude = -127;
+ else pXSector->amplitude = pXSource->data2;
+
+ }
+
+ if (valueIsBetween(pXSource->data3, -1, 32767)) {
+ if (pXSource->data3 > 255) pXSector->freq = 255;
+ else pXSector->freq = pXSource->data3;
+ }
+
+ if (valueIsBetween(pXSource->data4, -1, 65535)) {
+ if (pXSource->data4 > 255) pXSector->phase = 255;
+ else pXSector->phase = (short)pXSource->data4;
+ }
+
+ if ((pSource->hitag & kHitagExtBit) != 0)
+ pXSector->shadeAlways = true;
+
+ }
+
+ } else if (pSource->type == kGDXDudeTargetChanger) {
+
+ /* - Target changer for dudes via TX - */
+
+ /* - data1 = target dude data1 value (can be zero) - */
+ /* 666: attack everyone, even if data1 id does not fit, except mates (if any) - */
+ /* - data2 = 0: AI deathmatch mode - */
+ /* 1: AI team deathmatch mode - */
+ /* - data3 = 0: do not force target to fight dude back and *do not* awake some inactive monsters in sight - */
+ /* 1: force target to fight dude back and *do not* awake some inactive monsters in sight - */
+ /* 2: force target to fight dude back and awake some inactive monsters in sight - */
+ /* - data4 = 0: do not ignore player(s) (even if enough targets in sight) - */
+ /* 1: try to ignore player(s) (while enough targets in sight) - */
+ /* 2: ignore player(s) (attack only when no targets in sight at all) - */
+ /* 3: go to idle state if no targets in sight and ignore player(s) always - */
+ /* 4: follow player(s) when no targets in sight, attack targets if any in sight - */
+
+ if (type != 3 || !IsDudeSprite(&sprite[nDest]) || sprite[nDest].statnum != 6) return;
+ spritetype* pSprite = &sprite[nDest]; XSPRITE* pXSprite = &xsprite[pSprite->extra];
+ spritetype* pTarget = NULL; XSPRITE* pXTarget = NULL; int receiveHp = 33 + Random(33);
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->lotag - kDudeBase]; int matesPerEnemy = 1;
+
+ // dude is burning?
+ if (pXSprite->burnTime > 0 && pXSprite->burnSource >= 0 && pXSprite->burnSource < kMaxSprites) {
+ if (!IsBurningDude(pSprite))
+ {
+ spritetype* pBurnSource = &sprite[pXSprite->burnSource];
+ if (pBurnSource->extra >= 0) {
+ if (pXSource->data2 == 1 && isMateOf(pXSprite, &xsprite[pBurnSource->extra])) {
+ pXSprite->burnTime = 0;
+
+ // heal dude a bit in case of friendly fire
+ if (pXSprite->data4 > 0 && pXSprite->health < pXSprite->data4)
+ actHealDude(pXSprite, receiveHp, pXSprite->data4);
+ else if (pXSprite->health < pDudeInfo->startHealth)
+ actHealDude(pXSprite, receiveHp, pDudeInfo->startHealth);
+ }
+ else if (xsprite[pBurnSource->extra].health <= 0) {
+ pXSprite->burnTime = 0;
+ }
+ }
+ }
+ else {
+ actKillDude(pSource->xvel, pSprite, DAMAGE_TYPE_0, 65535);
+ return;
+ }
+ }
+
+ spritetype* pPlayer = targetIsPlayer(pXSprite);
+ // special handling for player(s) if target changer data4 > 2.
+ if (pPlayer != NULL) {
+ if (pXSource->data4 == 3) {
+ aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
+ aiSetGenIdleState(pSprite, pXSprite);
+ if (pSprite->lotag == kGDXDudeUniversalCultist)
+ removeLeech(leechIsDropped(pSprite));
+ }
+ else if (pXSource->data4 == 4) {
+ aiSetTarget(pXSprite, pPlayer->x, pPlayer->y, pPlayer->z);
+ if (pSprite->lotag == kGDXDudeUniversalCultist)
+ removeLeech(leechIsDropped(pSprite));
+ }
+ }
+
+ int maxAlarmDudes = 8 + Random(8);
+ if (pXSprite->target > -1 && sprite[pXSprite->target].extra > -1 && pPlayer == NULL) {
+ pTarget = &sprite[pXSprite->target]; pXTarget = &xsprite[pTarget->extra];
+
+ if (unitCanFly(pSprite) && isMeleeUnit(pTarget) && !unitCanFly(pTarget))
+ pSprite->hitag |= 0x0002;
+ else if (unitCanFly(pSprite))
+ pSprite->hitag &= ~0x0002;
+
+ if (!IsDudeSprite(pTarget) || pXTarget->health < 1 || !dudeCanSeeTarget(pXSprite, pDudeInfo, pTarget)) {
+ aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
+ }
+ // dude attack or attacked by target that does not fit by data id?
+ else if (pXSource->data1 != 666 && pXTarget->data1 != pXSource->data1) {
+ if (affectedByTargetChg(pXTarget)) {
+
+ // force stop attack target
+ aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
+ if (pXSprite->burnSource == pTarget->xvel) {
+ pXSprite->burnTime = 0;
+ pXSprite->burnSource = -1;
+ }
+
+ // force stop attack dude
+ aiSetTarget(pXTarget, pTarget->x, pTarget->y, pTarget->z);
+ if (pXTarget->burnSource == pSprite->xvel) {
+ pXTarget->burnTime = 0;
+ pXTarget->burnSource = -1;
+ }
+ }
+
+ }
+ // instantly kill annoying spiders, rats, hands etc if dude is big enough
+ else if (isAnnoyingUnit(pTarget) && !isAnnoyingUnit(pSprite) && tilesiz[pSprite->picnum].y >= 60 &&
+ getTargetDist(pSprite, pDudeInfo, pTarget) < 2) {
+
+ actKillDude(pSource->xvel, pTarget, DAMAGE_TYPE_0, 65535);
+ aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
+
+ }
+ else if (pXSource->data2 == 1 && isMateOf(pXSprite, pXTarget)) {
+ spritetype* pMate = pTarget; XSPRITE* pXMate = pXTarget;
+
+ // heal dude
+ if (pXSprite->data4 > 0 && pXSprite->health < pXSprite->data4)
+ actHealDude(pXSprite, receiveHp, pXSprite->data4);
+ else if (pXSprite->health < pDudeInfo->startHealth)
+ actHealDude(pXSprite, receiveHp, pDudeInfo->startHealth);
+
+ // heal mate
+ if (pXMate->data4 > 0 && pXMate->health < pXMate->data4)
+ actHealDude(pXMate, receiveHp, pXMate->data4);
+ else {
+ DUDEINFO* pTDudeInfo = &dudeInfo[pMate->lotag - kDudeBase];
+ if (pXMate->health < pTDudeInfo->startHealth)
+ actHealDude(pXMate, receiveHp, pTDudeInfo->startHealth);
+ }
+
+ if (pXMate->target > -1 && sprite[pXMate->target].extra >= 0) {
+ pTarget = &sprite[pXMate->target];
+ // force mate stop attack dude, if he does
+ if (pXMate->target == pSprite->xvel) {
+ aiSetTarget(pXMate, pMate->x, pMate->y, pMate->z);
+ }
+ else if (!isMateOf(pXSprite, &xsprite[pTarget->extra])) {
+ // force dude to attack same target that mate have
+ aiSetTarget(pXSprite, pTarget->xvel);
+ return;
+
+ }
+ else {
+ // force mate to stop attack another mate
+ aiSetTarget(pXMate, pMate->x, pMate->y, pMate->z);
+ }
+ }
+
+ // force dude stop attack mate, if target was not changed previously
+ if (pXSprite->target == pMate->xvel)
+ aiSetTarget(pXSprite, pSprite->x, pSprite->y, pSprite->z);
+
+
+ }
+ // check if targets aims player then force this target to fight with dude
+ else if (targetIsPlayer(pXTarget) != NULL) {
+ aiSetTarget(pXTarget, pSprite->xvel);
+ }
+
+ int mDist = 3; if (isMeleeUnit(pSprite)) mDist = 2;
+ if (pXSprite->target >= 0 && getTargetDist(pSprite, pDudeInfo, &sprite[pXSprite->target]) < mDist) {
+ if (!isActive(pSprite->xvel)) aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ // lets try to look for target that fits better by distance
+ else if ((gFrameClock & 256) != 0 && (pXSprite->target < 0 || getTargetDist(pSprite, pDudeInfo, pTarget) >= mDist)) {
+ pTarget = getTargetInRange(pSprite, 0, mDist, pXSource->data1, pXSource->data2);
+ if (pTarget != NULL) {
+ pXTarget = &xsprite[pTarget->extra];
+
+ // Make prev target not aim in dude
+ if (pXSprite->target > -1) {
+ spritetype* prvTarget = &sprite[pXSprite->target];
+ aiSetTarget(&xsprite[prvTarget->extra], prvTarget->x, prvTarget->y, prvTarget->z);
+ if (!isActive(pTarget->xvel))
+ aiActivateDude(pTarget, pXTarget);
+ }
+
+ // Change target for dude
+ aiSetTarget(pXSprite, pTarget->xvel);
+ if (!isActive(pSprite->xvel))
+ aiActivateDude(pSprite, pXSprite);
+
+ // ...and change target of target to dude to force it fight
+ if (pXSource->data3 > 0 && pXTarget->target != pSprite->xvel) {
+ aiSetTarget(pXTarget, pSprite->xvel);
+ if (!isActive(pTarget->xvel))
+ aiActivateDude(pTarget, pXTarget);
+ }
+ return;
+ }
+ }
+ }
+
+ if ((pXSprite->target < 0 || pPlayer != NULL) && (gFrameClock & 32) != 0) {
+ // try find first target that dude can see
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ pTarget = &sprite[nSprite]; pXTarget = &xsprite[pTarget->extra];
+
+ if (pXTarget->target == pSprite->xvel) {
+ aiSetTarget(pXSprite, pTarget->xvel);
+ return;
+ }
+
+ // skip non-dudes and players
+ if (!IsDudeSprite(pTarget) || (IsPlayerSprite(pTarget) && pXSource->data4 > 0) || pTarget->owner == pSprite->xvel) continue;
+ // avoid self aiming, those who dude can't see, and those who dude own
+ else if (!dudeCanSeeTarget(pXSprite, pDudeInfo, pTarget) || pSprite->xvel == pTarget->xvel) continue;
+ // if Target Changer have data1 = 666, everyone can be target, except AI team mates.
+ else if (pXSource->data1 != 666 && pXSource->data1 != pXTarget->data1) continue;
+ // don't attack immortal, burning dudes and mates
+ if (IsBurningDude(pTarget) || !IsKillableDude(pTarget, true) || (pXSource->data2 == 1 && isMateOf(pXSprite, pXTarget)))
+ continue;
+
+ if (pXSource->data2 == 0 || (pXSource->data2 == 1 && !isMatesHaveSameTarget(pXSprite, pTarget, matesPerEnemy))) {
+
+ // Change target for dude
+ aiSetTarget(pXSprite, pTarget->xvel);
+ if (!isActive(pSprite->xvel))
+ aiActivateDude(pSprite, pXSprite);
+
+ // ...and change target of target to dude to force it fight
+ if (pXSource->data3 > 0 && pXTarget->target != pSprite->xvel) {
+ aiSetTarget(pXTarget, pSprite->xvel);
+ if (!isActive(pTarget->xvel))
+ aiActivateDude(pTarget, pXTarget);
+
+ if (pXSource->data3 == 2)
+ disturbDudesInSight(pTarget, maxAlarmDudes);
+ }
+ return;
+ }
+ break;
+ }
+ }
+
+ // got no target - let's ask mates if they have targets
+ if ((pXSprite->target < 0 || pPlayer != NULL) && pXSource->data2 == 1 && (gFrameClock & 64) != 0) {
+ spritetype* pMateTarget = NULL;
+ if ((pMateTarget = getMateTargets(pXSprite)) != NULL && pMateTarget->extra > 0) {
+ XSPRITE* pXMateTarget = &xsprite[pMateTarget->extra];
+ if (dudeCanSeeTarget(pXSprite, pDudeInfo, pMateTarget)) {
+ if (pXMateTarget->target < 0) {
+ aiSetTarget(pXMateTarget, pSprite->xvel);
+ if (IsDudeSprite(pMateTarget) && !isActive(pMateTarget->xvel))
+ aiActivateDude(pMateTarget, pXMateTarget);
+ }
+
+ aiSetTarget(pXSprite, pMateTarget->xvel);
+ if (!isActive(pSprite->xvel))
+ aiActivateDude(pSprite, pXSprite);
+ return;
+
+ // try walk in mate direction in case if not see the target
+ }
+ else if (pXMateTarget->target >= 0 && dudeCanSeeTarget(pXSprite, pDudeInfo, &sprite[pXMateTarget->target])) {
+ spritetype* pMate = &sprite[pXMateTarget->target];
+ pXSprite->target = pMateTarget->xvel;
+ pXSprite->targetX = pMate->x;
+ pXSprite->targetY = pMate->y;
+ pXSprite->targetZ = pMate->z;
+ if (!isActive(pSprite->xvel))
+ aiActivateDude(pSprite, pXSprite);
+ return;
+ }
+ }
+ }
+
+ } else if (pSource->type == kGDXObjSizeChanger) {
+
+ /* - size and pan changer of sprite/wall/sector via TX ID - */
+ /* - data1 = sprite xrepeat / wall xrepeat / floor xpan - */
+ /* - data2 = sprite yrepeat / wall yrepeat / floor ypan - */
+ /* - data3 = sprite xoffset / wall xoffset / ceil xpan - */
+ /* - data3 = sprite yoffset / wall yoffset / ceil ypan - */
+
+ if (pXSource->data1 > 255) pXSource->data1 = 255;
+ if (pXSource->data2 > 255) pXSource->data2 = 255;
+ if (pXSource->data3 > 255) pXSource->data3 = 255;
+ if (valueIsBetween(pXSource->data4, 255, 65535))
+ pXSource->data4 = 255;
+
+ switch (type) {
+ // for sectors
+ case 6:
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ sector[nDest].floorxpanning = pXSource->data1;
+
+ if (valueIsBetween(pXSource->data2, -1, 32767))
+ sector[nDest].floorypanning = pXSource->data2;
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ sector[nDest].ceilingxpanning = pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535))
+ sector[nDest].ceilingypanning = (short)pXSource->data4;
+ break;
+ // for sprites
+ case 3:
+
+ if (valueIsBetween(pXSource->data1, -1, 32767) &&
+ valueIsBetween(pXSource->data2, -1, 32767) &&
+ pXSource->data1 < 4 && pXSource->data2 < 4)
+ {
+
+ sprite[nDest].xrepeat = 4;
+ sprite[nDest].yrepeat = 4;
+ sprite[nDest].cstat |= kSprInvisible;
+
+ }
+ else {
+
+ if (valueIsBetween(pXSource->data1, -1, 32767)) {
+ if (pXSource->data1 < 4)
+ sprite[nDest].xrepeat = 4;
+ else
+ sprite[nDest].xrepeat = pXSource->data1;
+ }
+
+ if (valueIsBetween(pXSource->data2, -1, 32767)) {
+ if (pXSource->data2 < 4)
+ sprite[nDest].yrepeat = 4;
+ else
+ sprite[nDest].yrepeat = pXSource->data2;
+ }
+ }
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ sprite[nDest].xoffset = pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535))
+ sprite[nDest].yoffset = (short)pXSource->data4;
+
+ break;
+ // for walls
+ case 0:
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ wall[nDest].xrepeat = pXSource->data1;
+
+ if (valueIsBetween(pXSource->data2, -1, 32767))
+ wall[nDest].yrepeat = pXSource->data2;
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ wall[nDest].xpanning = (short)pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535))
+ wall[nDest].ypanning = (short)pXSource->data4;
+
+ break;
+ }
+
+ } else if (pSource->type == kGDXObjPicnumChanger) {
+
+ /* - picnum changer can change picnum of sprite/wall/sector via TX ID - */
+ /* - data1 = sprite pic / wall pic / sector floor pic - */
+ /* - data3 = sprite pal / wall pal / sector floor pic - */
+
+ switch (type) {
+ // for sectors
+ case 6:
+ {
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ sector[nDest].floorpicnum = pXSource->data1;
+
+ if (valueIsBetween(pXSource->data2, -1, 32767))
+ sector[nDest].ceilingpicnum = pXSource->data2;
+
+ XSECTOR *pXSector = &xsector[sector[nDest].extra];
+ if (valueIsBetween(pXSource->data3, -1, 32767)) {
+ sector[nDest].floorpal = pXSource->data3;
+ if ((pSource->hitag & kHitagExtBit) != 0)
+ pXSector->floorpal = pXSource->data3;
+ }
+
+ if (valueIsBetween(pXSource->data4, -1, 65535)) {
+ sector[nDest].ceilingpal = (short)pXSource->data4;
+ if ((pSource->hitag & kHitagExtBit) != 0)
+ pXSector->ceilpal = (short)pXSource->data4;
+ }
+ break;
+ }
+ // for sprites
+ case 3:
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ sprite[nDest].picnum = pXSource->data1;
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ sprite[nDest].pal = pXSource->data3;
+ break;
+ // for walls
+ case 0:
+ if (valueIsBetween(pXSource->data1, -1, 32767))
+ wall[nDest].picnum = pXSource->data1;
+
+ if (valueIsBetween(pXSource->data2, -1, 32767))
+ wall[nDest].overpicnum = pXSource->data2;
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ wall[nDest].pal = pXSource->data3;
+ break;
+ }
+
+ } else if (pSource->type == kGDXObjPropertiesChanger) {
+
+ /* - properties changer can change various properties of sprite/wall/sector via TX ID - */
+ /* - data1 = sector underwater status - */
+ /* - data2 = sector visibility - */
+ /* - data3 = sector ceiling cstat / sprite / wall hitag - */
+ /* - data4 = sector floor / sprite / wall cstat - */
+
+ switch (type) {
+ // for sectors
+ case 6:
+ {
+ XSECTOR* pXSector = &xsector[sector[nDest].extra];
+
+ switch (pXSource->data1) {
+ case 0:
+ pXSector->Underwater = false;
+ break;
+ case 1:
+ pXSector->Underwater = true;
+ break;
+ case 2:
+ pXSector->Depth = 0;
+ break;
+ case 3:
+ pXSector->Depth = 1;
+ break;
+ case 4:
+ pXSector->Depth = 2;
+ break;
+ case 5:
+ pXSector->Depth = 3;
+ break;
+ case 6:
+ pXSector->Depth = 4;
+ break;
+ case 7:
+ pXSector->Depth = 5;
+ break;
+ case 8:
+ pXSector->Depth = 6;
+ break;
+ case 9:
+ pXSector->Depth = 7;
+ break;
+ }
+
+ if (valueIsBetween(pXSource->data2, -1, 32767)) {
+ if (pXSource->data2 > 255) sector[nDest].visibility = 255;
+ else sector[nDest].visibility = pXSource->data2;
+ }
+
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ sector[nDest].ceilingstat = pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535))
+ sector[nDest].floorstat = pXSource->data4;
+ break;
+ }
+ // for sprites
+ case 3:
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ sprite[nDest].hitag = pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535)) {
+ pXSource->data4 |= kSprOriginAlign;
+ sprite[nDest].cstat = pXSource->data4;
+ }
+ break;
+ // for walls
+ case 0:
+ if (valueIsBetween(pXSource->data3, -1, 32767))
+ wall[nDest].hitag = pXSource->data3;
+
+ if (valueIsBetween(pXSource->data4, -1, 65535))
+ wall[nDest].cstat = pXSource->data4;
+ break;
+ }
+ }
+}
+// By NoOne: the following functions required for kGDXDudeTargetChanger
+//---------------------------------------
+spritetype* getTargetInRange(spritetype* pSprite, int minDist, int maxDist, short data, short teamMode) {
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->type - kDudeBase]; XSPRITE* pXSprite = &xsprite[pSprite->extra];
+ spritetype* pTarget = NULL; XSPRITE* pXTarget = NULL; spritetype* cTarget = NULL;
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ pTarget = &sprite[nSprite]; pXTarget = &xsprite[pTarget->extra];
+ if (!dudeCanSeeTarget(pXSprite, pDudeInfo, pTarget)) continue;
+
+ int dist = getTargetDist(pSprite, pDudeInfo, pTarget);
+ if (dist < minDist || dist > maxDist) continue;
+ else if (pXSprite->target == pTarget->xvel) return pTarget;
+ else if (!IsDudeSprite(pTarget) || pTarget->xvel == pSprite->xvel || IsPlayerSprite(pTarget)) continue;
+ else if (IsBurningDude(pTarget) || !IsKillableDude(pTarget, true) || pTarget->owner == pSprite->xvel) continue;
+ else if ((teamMode == 1 && isMateOf(pXSprite, pXTarget)) || isMatesHaveSameTarget(pXSprite,pTarget,1)) continue;
+ else if (data == 666 || pXTarget->data1 == data) {
+
+ if (pXSprite->target > 0) {
+ cTarget = &sprite[pXSprite->target];
+ int fineDist1 = getFineTargetDist(pSprite, cTarget);
+ int fineDist2 = getFineTargetDist(pSprite, pTarget);
+ if (fineDist1 < fineDist2)
+ continue;
+ }
+ return pTarget;
+ }
+ }
+
+ return NULL;
+}
+
+bool isMateOf(XSPRITE* pXDude, XSPRITE* pXSprite) {
+ return (pXDude->rxID == pXSprite->rxID);
+}
+
+spritetype* targetIsPlayer(XSPRITE* pXSprite) {
+
+ if (pXSprite->target >= 0) {
+ if (IsPlayerSprite(&sprite[pXSprite->target]))
+ return &sprite[pXSprite->target];
+ }
+
+ return NULL;
+}
+
+bool isTargetAimsDude(XSPRITE* pXTarget, spritetype* pDude) {
+ return (pXTarget->target == pDude->xvel);
+}
+
+spritetype* getMateTargets(XSPRITE* pXSprite) {
+ int rx = pXSprite->rxID; spritetype* pMate = NULL; XSPRITE* pXMate = NULL;
+
+ for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
+ if (rxBucket[i].type == 3) {
+ pMate = &sprite[rxBucket[i].index];
+ if (pMate->extra < 0 || pMate->xvel == sprite[pXSprite->reference].xvel || !IsDudeSprite(pMate))
+ continue;
+
+ pXMate = &xsprite[pMate->extra];
+ if (pXMate->target > -1) {
+ if (!IsPlayerSprite(&sprite[pXMate->target]))
+ return &sprite[pXMate->target];
+ }
+
+ }
+ }
+
+ return NULL;
+}
+
+bool isMatesHaveSameTarget(XSPRITE* pXLeader, spritetype* pTarget, int allow) {
+ int rx = pXLeader->rxID; spritetype* pMate = NULL; XSPRITE* pXMate = NULL;
+
+ for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
+
+ if (rxBucket[i].type != 3)
+ continue;
+
+ pMate = &sprite[rxBucket[i].index];
+ if (pMate->extra < 0 || pMate->xvel == sprite[pXLeader->reference].xvel || !IsDudeSprite(pMate))
+ continue;
+
+ pXMate = &xsprite[pMate->extra];
+ if (pXMate->target == pTarget->xvel && allow-- <= 0)
+ return true;
+ }
+
+ return false;
+
+}
+
+bool isActive(int nSprite) {
+ spritetype* pDude = &sprite[nSprite]; XSPRITE* pXDude = &xsprite[pDude->extra];
+ int stateType = pXDude->aiState->stateType;
+ switch (stateType) {
+ case kAiStateIdle:
+ case kAiStateGenIdle:
+ case kAiStateSearch:
+ case kAiStateMove:
+ case kAiStateOther:
+ return false;
+ }
+ return true;
+}
+
+bool dudeCanSeeTarget(XSPRITE* pXDude, DUDEINFO* pDudeInfo, spritetype* pTarget) {
+ spritetype* pDude = &sprite[pXDude->reference];
+ int dx = pTarget->x - pDude->x; int dy = pTarget->y - pDude->y;
+
+ // check target
+ if (approxDist(dx, dy) < pDudeInfo->seeDist) {
+ int eyeAboveZ = pDudeInfo->eyeHeight * pDude->yrepeat << 2;
+
+ // is there a line of sight to the target?
+ if (cansee(pDude->x, pDude->y, pDude->z, pDude->sectnum, pTarget->x, pTarget->y, pTarget->z - eyeAboveZ, pTarget->sectnum)) {
+ /*int nAngle = getangle(dx, dy);
+ int losAngle = ((1024 + nAngle - pDude->ang) & 2047) - 1024;
+
+ // is the target visible?
+ if (klabs(losAngle) < 2048) // 360 deg periphery here*/
+ return true;
+ }
+ }
+
+ return false;
+
+}
+
+// by NoOne: this function required if monsters in genIdle ai state. It wakes up monsters
+// when kGDXDudeTargetChanger goes to off state, so they won't ignore the world.
+void activateDudes(int rx) {
+ for (int i = bucketHead[rx]; i < bucketHead[rx + 1]; i++) {
+ if (rxBucket[i].type != 3) continue;
+ spritetype * pDude = &sprite[rxBucket[i].index]; XSPRITE * pXDude = &xsprite[pDude->extra];
+ if (!IsDudeSprite(pDude) || pXDude->aiState->stateType != kAiStateGenIdle) continue;
+ aiInitSprite(pDude);
+ }
+}
+
+bool affectedByTargetChg(XSPRITE* pXDude) {
+ if (pXDude->rxID <= 0 || pXDude->locked == 1) return false;
+ for (int nSprite = headspritestat[kStatGDXDudeTargetChanger]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ XSPRITE* pXSprite = (sprite[nSprite].extra >= 0) ? &xsprite[sprite[nSprite].extra] : NULL;
+ if (pXSprite == NULL || pXSprite->txID <= 0 || pXSprite->state != 1) continue;
+ for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
+ if (rxBucket[i].type != 3) continue;
+
+ spritetype* pSprite = &sprite[rxBucket[i].index];
+ if (pSprite->extra < 0 || !IsDudeSprite(pSprite)) continue;
+ else if (pSprite->xvel == sprite[pXDude->reference].xvel) return true;
+ }
+ }
+ return false;
+}
+
+int getDataFieldOfObject(int objType, int objIndex, int dataIndex) {
+ int data = -65535;
+ switch (objType) {
+ case 3:
+ switch (dataIndex) {
+ case 1:
+ return xsprite[sprite[objIndex].extra].data1;
+ case 2:
+ return xsprite[sprite[objIndex].extra].data2;
+ case 3:
+ return xsprite[sprite[objIndex].extra].data3;
+ case 4:
+ return xsprite[sprite[objIndex].extra].data4;
+ default:
+ return data;
+ }
+ case 0:
+ return xsector[sector[objIndex].extra].data;
+ case 6:
+ return xwall[wall[objIndex].extra].data;
+ default:
+ return data;
+ }
+}
+
+bool setDataValueOfObject(int objType, int objIndex, int dataIndex, int value) {
+ switch (objType) {
+ case 3:
+ switch (dataIndex) {
+ case 1:
+ xsprite[sprite[objIndex].extra].data1 = value;
+ return true;
+ case 2:
+ xsprite[sprite[objIndex].extra].data2 = value;
+ return true;
+ case 3:
+ xsprite[sprite[objIndex].extra].data3 = value;
+ return true;
+ case 4:
+ xsprite[sprite[objIndex].extra].data4 = value;
+ return true;
+ default:
+ return false;
+ }
+ case 0:
+ xsector[sector[objIndex].extra].data = value;
+ return true;
+ case 6:
+ xwall[wall[objIndex].extra].data = value;
+ return true;
+ default:
+ return false;
+ }
+}
+
+// by NoOne: this function checks if all TX objects have the same value
+bool goalValueIsReached(XSPRITE* pXSprite) {
+ for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
+ if (getDataFieldOfObject(rxBucket[i].type, rxBucket[i].index, pXSprite->data1) != pXSprite->data4)
+ return false;
+ }
+ return true;
+}
+
+// by NoOne: this function tells if there any dude found for kGDXDudeTargetChanger
+bool getDudesForTargetChg(XSPRITE* pXSprite) {
+ for (int i = bucketHead[pXSprite->txID]; i < bucketHead[pXSprite->txID + 1]; i++) {
+ if (rxBucket[i].type != 3) continue;
+ else if (IsDudeSprite(&sprite[rxBucket[i].index]) &&
+ xsprite[sprite[rxBucket[i].index].extra].health > 0) return true;
+ }
+
+ return false;
+}
+
+void disturbDudesInSight(spritetype* pSprite, int max) {
+ spritetype* pDude = NULL; XSPRITE* pXDude = NULL;
+ XSPRITE* pXSprite = &xsprite[pSprite->extra];
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->lotag - kDudeBase];
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite]) {
+ pDude = &sprite[nSprite];
+ if (pDude->xvel == pSprite->xvel || !IsDudeSprite(pDude) || pDude->extra < 0)
+ continue;
+ pXDude = &xsprite[pDude->extra];
+ if (dudeCanSeeTarget(pXSprite, pDudeInfo, pDude)) {
+ if (pXDude->target != -1 || pXDude->rxID > 0)
+ continue;
+
+ aiSetTarget(pXDude, pDude->x, pDude->y, pDude->z);
+ aiActivateDude(pDude, pXDude);
+ if (max-- < 1)
+ break;
+ }
+ }
+}
+
+int getTargetDist(spritetype* pSprite, DUDEINFO* pDudeInfo, spritetype* pTarget) {
+ int x = pTarget->x; int y = pTarget->y;
+ int dx = x - pSprite->x; int dy = y - pSprite->y;
+
+ int dist = approxDist(dx, dy);
+ if (dist <= pDudeInfo->meleeDist) return 0;
+ if (dist >= pDudeInfo->seeDist) return 13;
+ if (dist <= pDudeInfo->seeDist / 12) return 1;
+ if (dist <= pDudeInfo->seeDist / 11) return 2;
+ if (dist <= pDudeInfo->seeDist / 10) return 3;
+ if (dist <= pDudeInfo->seeDist / 9) return 4;
+ if (dist <= pDudeInfo->seeDist / 8) return 5;
+ if (dist <= pDudeInfo->seeDist / 7) return 6;
+ if (dist <= pDudeInfo->seeDist / 6) return 7;
+ if (dist <= pDudeInfo->seeDist / 5) return 8;
+ if (dist <= pDudeInfo->seeDist / 4) return 9;
+ if (dist <= pDudeInfo->seeDist / 3) return 10;
+ if (dist <= pDudeInfo->seeDist / 2) return 11;
+ return 12;
+}
+
+int getFineTargetDist(spritetype* pSprite, spritetype* pTarget) {
+ int x = pTarget->x; int y = pTarget->y;
+ int dx = x - pSprite->x; int dy = y - pSprite->y;
+
+ int dist = approxDist(dx, dy);
+ return dist;
+}
+
+bool IsBurningDude(spritetype* pSprite) {
+ if (pSprite == NULL) return false;
+ switch (pSprite->type) {
+ case 239: // burning dude
+ case 240: // cultist burning
+ case 241: // axe zombie burning
+ case 242: // fat zombie burning
+ case 252: // tiny caleb burning
+ case 253: // beast burning
+ case kGDXGenDudeBurning:
+ return true;
+ }
+
+ return false;
+}
+
+bool IsKillableDude(spritetype* pSprite, bool locked) {
+ if (!IsDudeSprite(pSprite)) return false;
+ DUDEINFO* pDudeInfo = &dudeInfo[pSprite->lotag - kDudeBase];
+
+ // Optionally check if dude is locked
+ if (locked && xsprite[pSprite->extra].locked == 1)
+ return false;
+
+
+ // Make sure damage shift is greater than 0;
+ int a = 0;
+ for (int i = 0; i <= 6; i++) {
+ a += pDudeInfo->startDamage[i];
+ }
+
+ if (a == 0) return false;
+ return true;
+}
+
+bool isAnnoyingUnit(spritetype* pDude) {
+ switch (pDude->lotag) {
+ case 212: // hand
+ case 213: // brown spider
+ case 214: // red spider
+ case 215: // black spider
+ case 216: // mother spider
+ case 218: // eel
+ case 219: // bat
+ case 220: // rat
+ case 222: // green tentacle
+ case 224: // fire tentacle
+ case 226: // mother tentacle
+ case 223: // green pod
+ case 225: // fire pod
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool unitCanFly(spritetype* pDude) {
+ switch (pDude->lotag) {
+ case 219: // bat
+ case 206: // gargoyle
+ case 207: // stone gargoyle
+ case 210: // phantasm
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isMeleeUnit(spritetype* pDude) {
+ switch (pDude->lotag) {
+ case 203: // axe zombie
+ case 205: // earth zombie
+ case 206: // gargoyle
+ case 212: // hand
+ case 213: // brown spider
+ case 214: // red spider
+ case 215: // black spider
+ case 216: // mother spider
+ case 217: // gill beast
+ case 218: // eel
+ case 219: // bat
+ case 220: // rat
+ case 222: // green tentacle
+ case 224: // fire tentacle
+ case 226: // mother tentacle
+ case 244: // sleep zombie
+ case 245: // innocent
+ case 250: // tiny caleb
+ case 251: // beast
+ return true;
+ case kGDXDudeUniversalCultist:
+ return (pDude->extra >= 0 && dudeIsMelee(&xsprite[pDude->extra]));
+ default:
+ return false;
+ }
+}
+//---------------------------------------
+
+void ProcessMotion(void)
+{
+ sectortype *pSector;
+ int nSector;
+ for (pSector = sector, nSector = 0; nSector < numsectors; nSector++, pSector++)
+ {
+ int nXSector = pSector->extra;
+ if (nXSector <= 0)
+ continue;
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (pXSector->bobSpeed != 0)
+ {
+ if (pXSector->bobAlways)
+ pXSector->bobTheta += pXSector->bobSpeed;
+ else if (pXSector->busy == 0)
+ continue;
+ else
+ pXSector->bobTheta += mulscale16(pXSector->bobSpeed, pXSector->busy);
+ int vdi = mulscale30(Sin(pXSector->bobTheta), pXSector->bobZRange<<8);
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->cstat&24576)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z += vdi;
+ }
+ }
+ if (pXSector->bobFloor)
+ {
+ int floorZ = pSector->floorz;
+ viewInterpolateSector(nSector, pSector);
+ pSector->floorz = baseFloor[nSector]+vdi;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&2)
+ pSprite->hitag |= 4;
+ else
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (bottom >= floorZ && (pSprite->cstat&48) == 0)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z += vdi;
+ }
+ }
+ }
+ }
+ if (pXSector->bobCeiling)
+ {
+ int ceilZ = pSector->ceilingz;
+ viewInterpolateSector(nSector, pSector);
+ pSector->ceilingz = baseCeil[nSector]+vdi;
+ for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (top <= ceilZ && (pSprite->cstat&48) == 0)
+ {
+ viewBackupSpriteLoc(nSprite, pSprite);
+ pSprite->z += vdi;
+ }
+ }
+ }
+ }
+ }
+}
+
+void AlignSlopes(void)
+{
+ sectortype *pSector;
+ int nSector;
+ for (pSector = sector, nSector = 0; nSector < numsectors; nSector++, pSector++)
+ {
+ if (qsector_filler[nSector])
+ {
+ walltype *pWall = &wall[pSector->wallptr+qsector_filler[nSector]];
+ walltype *pWall2 = &wall[pWall->point2];
+ int nNextSector = pWall->nextsector;
+ if (nNextSector >= 0)
+ {
+ int x = (pWall->x+pWall2->x)/2;
+ int y = (pWall->y+pWall2->y)/2;
+ viewInterpolateSector(nSector, pSector);
+ alignflorslope(nSector, x, y, getflorzofslope(nNextSector, x, y));
+ alignceilslope(nSector, x, y, getceilzofslope(nNextSector, x, y));
+ }
+ }
+ }
+}
+
+int(*gBusyProc[])(unsigned int, unsigned int) =
+{
+ VCrushBusy,
+ VSpriteBusy,
+ VDoorBusy,
+ HDoorBusy,
+ RDoorBusy,
+ StepRotateBusy,
+ GenSectorBusy,
+ PathBusy
+};
+
+void trProcessBusy(void)
+{
+ memset(velFloor, 0, sizeof(velFloor));
+ memset(velCeil, 0, sizeof(velCeil));
+ for (int i = gBusyCount-1; i >= 0; i--)
+ {
+ int oldBusy = gBusy[i].at8;
+ gBusy[i].at8 = ClipRange(oldBusy+gBusy[i].at4*4, 0, 65536);
+ int nStatus = gBusyProc[gBusy[i].atc](gBusy[i].at0, gBusy[i].at8);
+ switch (nStatus)
+ {
+ case 1:
+ gBusy[i].at8 = oldBusy;
+ break;
+ case 2:
+ gBusy[i].at8 = oldBusy;
+ gBusy[i].at4 = -gBusy[i].at4;
+ break;
+ case 3:
+ gBusy[i] = gBusy[--gBusyCount];
+ break;
+ }
+ }
+ ProcessMotion();
+ AlignSlopes();
+}
+
+void InitGenerator(int);
+
+void trInit(void)
+{
+ gBusyCount = 0;
+ for (int i = 0; i < numwalls; i++)
+ {
+ baseWall[i].x = wall[i].x;
+ baseWall[i].y = wall[i].y;
+ }
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ if (sprite[i].statnum < kStatFree)
+ {
+ sprite[i].zvel = sprite[i].type;
+ baseSprite[i].x = sprite[i].x;
+ baseSprite[i].y = sprite[i].y;
+ baseSprite[i].z = sprite[i].z;
+ }
+ else
+ sprite[i].zvel = -1;
+ }
+ for (int i = 0; i < numwalls; i++)
+ {
+ int nXWall = wall[i].extra;
+ dassert(nXWall < kMaxXWalls);
+ if (nXWall > 0)
+ {
+ XWALL *pXWall = &xwall[nXWall];
+ if (pXWall->state)
+ pXWall->busy = 65536;
+ }
+ }
+ dassert((numsectors >= 0) && (numsectors < kMaxSectors));
+ for (int i = 0; i < numsectors; i++)
+ {
+ sectortype *pSector = §or[i];
+ baseFloor[i] = pSector->floorz;
+ baseCeil[i] = pSector->ceilingz;
+ int nXSector = pSector->extra;
+ if (nXSector > 0)
+ {
+ dassert(nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (pXSector->state)
+ pXSector->busy = 65536;
+ switch (pSector->lotag)
+ {
+ case kSecCounter:
+ //By NoOne: no need to trigger once it, instead lock so it can be unlocked and used again.
+ //pXSector->triggerOnce = 1;
+ evPost(i, 6, 0, CALLBACK_ID_12);
+ break;
+ case 600:
+ case 602:
+ ZTranslateSector(i, pXSector, pXSector->busy, 1);
+ break;
+ case 614:
+ case 616:
+ {
+ spritetype *pSprite1 = &sprite[pXSector->at2c_0];
+ spritetype *pSprite2 = &sprite[pXSector->at2e_0];
+ TranslateSector(i, 0, -65536, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->lotag == 616);
+ for (int j = 0; j < pSector->wallnum; j++)
+ {
+ baseWall[pSector->wallptr+j].x = wall[pSector->wallptr+j].x;
+ baseWall[pSector->wallptr+j].y = wall[pSector->wallptr+j].y;
+ }
+ for (int nSprite = headspritesect[i]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ baseSprite[nSprite].x = sprite[nSprite].x;
+ baseSprite[nSprite].y = sprite[nSprite].y;
+ baseSprite[nSprite].z = sprite[nSprite].z;
+ }
+ TranslateSector(i, 0, pXSector->busy, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, pSprite1->ang, pSprite2->x, pSprite2->y, pSprite2->ang, pSector->lotag == 616);
+ ZTranslateSector(i, pXSector, pXSector->busy, 1);
+ break;
+ }
+ case 615:
+ case 617:
+ {
+ spritetype *pSprite1 = &sprite[pXSector->at2c_0];
+ TranslateSector(i, 0, -65536, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, 0, pSprite1->x, pSprite1->y, pSprite1->ang, pSector->lotag == 617);
+ for (int j = 0; j < pSector->wallnum; j++)
+ {
+ baseWall[pSector->wallptr+j].x = wall[pSector->wallptr+j].x;
+ baseWall[pSector->wallptr+j].y = wall[pSector->wallptr+j].y;
+ }
+ for (int nSprite = headspritesect[i]; nSprite >= 0; nSprite = nextspritesect[nSprite])
+ {
+ baseSprite[nSprite].x = sprite[nSprite].x;
+ baseSprite[nSprite].y = sprite[nSprite].y;
+ baseSprite[nSprite].z = sprite[nSprite].z;
+ }
+ TranslateSector(i, 0, pXSector->busy, pSprite1->x, pSprite1->y, pSprite1->x, pSprite1->y, 0, pSprite1->x, pSprite1->y, pSprite1->ang, pSector->lotag == 617);
+ ZTranslateSector(i, pXSector, pXSector->busy, 1);
+ break;
+ }
+ case 612:
+ InitPath(i, pXSector);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ for (int i = 0; i < kMaxSprites; i++)
+ {
+ int nXSprite = sprite[i].extra;
+ if (sprite[i].statnum < kStatFree && nXSprite > 0)
+ {
+ dassert(nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->state)
+ pXSprite->busy = 65536;
+ switch (sprite[i].type)
+ {
+ case 23:
+ pXSprite->triggerOnce = 1;
+ break;
+ case kGDXSequentialTX:
+ break;
+ case kGDXSeqSpawner:
+ case kGDXDudeTargetChanger:
+ case kGDXEffectSpawner:
+ case kGDXWindGenerator:
+ case 700:
+ case 701:
+ case 702:
+ case 703:
+ case 704:
+ case 705:
+ case 706:
+ case 707:
+ case 708:
+ InitGenerator(i);
+ break;
+ case 401:
+ case kGDXThingTNTProx:
+ pXSprite->Proximity = 1;
+ break;
+ case 414:
+ if (pXSprite->state)
+ sprite[i].hitag |= 7;
+ else
+ sprite[i].hitag &= ~7;
+ break;
+ }
+ if (pXSprite->Vector)
+ sprite[i].cstat |= 256;
+ if (pXSprite->Push)
+ sprite[i].cstat |= 4096;
+ }
+ }
+ evSend(0, 0, 7, COMMAND_ID_1);
+ if (gGameOptions.nGameType == 1)
+ evSend(0, 0, 9, COMMAND_ID_1);
+ else if (gGameOptions.nGameType == 2)
+ evSend(0, 0, 8, COMMAND_ID_1);
+ else if (gGameOptions.nGameType == 3)
+ {
+ evSend(0, 0, 8, COMMAND_ID_1);
+ evSend(0, 0, 10, COMMAND_ID_1);
+ }
+}
+
+void trTextOver(int nId)
+{
+ char *pzMessage = levelGetMessage(nId);
+ if (pzMessage)
+ viewSetMessage(pzMessage);
+}
+
+void InitGenerator(int nSprite)
+{
+ dassert(nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ dassert(pSprite->statnum != kMaxStatus);
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ switch (sprite[nSprite].type)
+ {
+ // By NoOne: intialize GDX generators
+ case kGDXDudeTargetChanger:
+ pSprite->cstat &= ~kSprBlock;
+ pSprite->cstat |= kSprInvisible;
+ if (pXSprite->busyTime <= 0) pXSprite->busyTime = 5;
+ if (pXSprite->state != pXSprite->restState)
+ evPost(nSprite, 3, 0, COMMAND_ID_21);
+ return;
+ case kGDXObjDataAccumulator:
+ case kGDXSeqSpawner:
+ case kGDXEffectSpawner:
+ pSprite->cstat &= ~kSprBlock;
+ pSprite->cstat |= kSprInvisible;
+ if (pXSprite->state != pXSprite->restState)
+ evPost(nSprite, 3, 0, COMMAND_ID_21);
+ return;
+ case 700:
+ pSprite->cstat &= ~kSprBlock;
+ pSprite->cstat |= kSprInvisible;
+ break;
+ }
+ if (pXSprite->state != pXSprite->restState && pXSprite->busyTime > 0)
+ evPost(nSprite, 3, (120*(pXSprite->busyTime+Random2(pXSprite->data1)))/10, COMMAND_ID_21);
+}
+
+void ActivateGenerator(int nSprite)
+{
+ dassert(nSprite < kMaxSprites);
+ spritetype *pSprite = &sprite[nSprite];
+ dassert(pSprite->statnum != kMaxStatus);
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ switch (pSprite->type) {
+ case 701:
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ actSpawnThing(pSprite->sectnum, pSprite->x, pSprite->y, bottom, 423);
+ break;
+ }
+ case 702:
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ actSpawnThing(pSprite->sectnum, pSprite->x, pSprite->y, bottom, 424);
+ break;
+ }
+ case 708:
+ {
+ // By NoOne: allow custom pitch for sounds in SFX gen.
+ int pitch = pXSprite->data4 << 1; if (pitch < 2000) pitch = 0;
+ sfxPlay3DSoundCP(pSprite, pXSprite->data2, -1, 0, pitch);
+ break;
+ }
+ case 703:
+ switch (pXSprite->data2)
+ {
+ case 0:
+ FireballTrapSeqCallback(3, nXSprite);
+ break;
+ case 1:
+ seqSpawn(35, 3, nXSprite, nFireballTrapClient);
+ break;
+ case 2:
+ seqSpawn(36, 3, nXSprite, nFireballTrapClient);
+ break;
+ }
+ break;
+ // By NoOne: EctoSkull gen can now fire any missile
+ case 704:
+ switch (pXSprite->data2)
+ {
+ case 0:
+ UniMissileTrapSeqCallback(3, nXSprite);
+ break;
+ case 1:
+ seqSpawn(35, 3, nXSprite, nUniMissileTrapClient);
+ break;
+ case 2:
+ seqSpawn(36, 3, nXSprite, nUniMissileTrapClient);
+ break;
+ }
+ break;
+ case 706:
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ gFX.fxSpawn(FX_23, pSprite->sectnum, pSprite->x, pSprite->y, top, 0);
+ break;
+ }
+ case 707:
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ gFX.fxSpawn(FX_26, pSprite->sectnum, pSprite->x, pSprite->y, top, 0);
+ break;
+ }
+ }
+}
+
+void FireballTrapSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->cstat&32)
+ actFireMissile(pSprite, 0, 0, 0, 0, (pSprite->cstat&8) ? 0x4000 : -0x4000, 305);
+ else
+ actFireMissile(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, 305);
+}
+
+// By NoOne: Callback for trap that can fire any missile specified in data3
+void UniMissileTrapSeqCallback(int, int nXSprite)
+{
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nSprite = pXSprite->reference;
+ spritetype *pSprite = &sprite[nSprite];
+
+ int nMissile = 307;
+ if (pXSprite->data3 >= kMissileBase && pXSprite->data3 < kMissileMax)
+ nMissile = pXSprite->data3;
+ else
+ return;
+
+ if (pSprite->cstat&32)
+ actFireMissile(pSprite, 0, 0, 0, 0, (pSprite->cstat&8) ? 0x4000 : -0x4000, nMissile);
+ else
+ actFireMissile(pSprite, 0, 0, Cos(pSprite->ang)>>16, Sin(pSprite->ang)>>16, 0, nMissile);
+}
+
+void MGunFireSeqCallback(int, int nXSprite)
+{
+ int nSprite = xsprite[nXSprite].reference;
+ spritetype *pSprite = &sprite[nSprite];
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pXSprite->data2 > 0 || pXSprite->data1 == 0)
+ {
+ if (pXSprite->data2 > 0)
+ {
+ pXSprite->data2--;
+ if (pXSprite->data2 == 0)
+ evPost(nSprite, 3, 1, COMMAND_ID_0);
+ }
+ int dx = (Cos(pSprite->ang)>>16)+Random2(1000);
+ int dy = (Sin(pSprite->ang)>>16)+Random2(1000);
+ int dz = Random2(1000);
+ actFireVector(pSprite, 0, 0, dx, dy, dz, VECTOR_TYPE_2);
+ sfxPlay3DSound(pSprite, 359, -1, 0);
+ }
+}
+
+void MGunOpenSeqCallback(int, int nXSprite)
+{
+ seqSpawn(39, 3, nXSprite, nMGunFireClient);
+}
+
+class TriggersLoadSave : public LoadSave
+{
+public:
+ virtual void Load();
+ virtual void Save();
+};
+
+void TriggersLoadSave::Load()
+{
+ Read(&gBusyCount, sizeof(gBusyCount));
+ Read(gBusy, sizeof(gBusy));
+ Read(basePath, sizeof(basePath));
+}
+
+void TriggersLoadSave::Save()
+{
+ Write(&gBusyCount, sizeof(gBusyCount));
+ Write(gBusy, sizeof(gBusy));
+ Write(basePath, sizeof(basePath));
+}
+
+static TriggersLoadSave *myLoadSave;
+
+void TriggersLoadSaveConstruct(void)
+{
+ myLoadSave = new TriggersLoadSave();
+}
diff --git a/source/blood/src/triggers.h b/source/blood/src/triggers.h
new file mode 100644
index 000000000..498f42659
--- /dev/null
+++ b/source/blood/src/triggers.h
@@ -0,0 +1,73 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "build.h"
+#include "common.h"
+#include "common_game.h"
+
+#include "blood.h"
+#include "db.h"
+#include "eventq.h"
+#include "dude.h"
+
+void trTriggerSector(unsigned int nSector, XSECTOR *pXSector, int a3);
+void trMessageSector(unsigned int nSector, EVENT a2);
+void trTriggerWall(unsigned int nWall, XWALL *pXWall, int a3);
+void trMessageWall(unsigned int nWall, EVENT a2);
+void trTriggerSprite(unsigned int nSprite, XSPRITE *pXSprite, int a3);
+void trMessageSprite(unsigned int nSprite, EVENT a2);
+void trProcessBusy(void);
+void trInit(void);
+void trTextOver(int nId);
+
+// By NoOne: functions required for new features
+// -------------------------------------------------------
+void pastePropertiesInObj(int type, int nDest, EVENT event);
+void trDamageSprite(int type, int nDest, EVENT event);
+spritetype* getTargetInRange(spritetype* pSprite, int minDist, int maxDist, short data, short teamMode);
+bool isMateOf(XSPRITE* pXDude, XSPRITE* pXSprite);
+spritetype* targetIsPlayer(XSPRITE* pXSprite);
+bool isTargetAimsDude(XSPRITE* pXTarget, spritetype* pDude);
+spritetype* getMateTargets(XSPRITE* pXSprite);
+bool isMatesHaveSameTarget(XSPRITE* pXLeader, spritetype* pTarget, int allow);
+bool isActive(int nSprite);
+bool dudeCanSeeTarget(XSPRITE* pXDude, DUDEINFO* pDudeInfo, spritetype* pTarget);
+void disturbDudesInSight(spritetype* pSprite, int max);
+int getTargetDist(spritetype* pSprite, DUDEINFO* pDudeInfo, spritetype* pTarget);
+int getFineTargetDist(spritetype* pSprite, spritetype* pTarget);
+bool IsBurningDude(spritetype* pSprite);
+bool IsKillableDude(spritetype* pSprite, bool locked);
+bool isAnnoyingUnit(spritetype* pDude);
+bool unitCanFly(spritetype* pDude);
+bool isMeleeUnit(spritetype* pDude);
+void activateDudes(int rx);
+bool affectedByTargetChg(XSPRITE* pXDude);
+int getDataFieldOfObject(int objType, int objIndex, int dataIndex);
+bool setDataValueOfObject(int objType, int objIndex, int dataIndex, int value);
+bool goalValueIsReached(XSPRITE* pXSprite);
+bool getDudesForTargetChg(XSPRITE* pXSprite);
+void stopWindOnSectors(XSPRITE* pXSource);
+void useSectorWindGen(XSPRITE* pXSource, sectortype* pSector);
+void useEffectGen(XSPRITE* pXSource, spritetype* pSprite);
+void useSeqSpawnerGen(XSPRITE* pXSource, spritetype* pSprite);
+// -------------------------------------------------------
\ No newline at end of file
diff --git a/source/blood/src/view.cpp b/source/blood/src/view.cpp
new file mode 100644
index 000000000..71812b023
--- /dev/null
+++ b/source/blood/src/view.cpp
@@ -0,0 +1,3818 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+
+#include "compat.h"
+#include "a.h"
+#include "build.h"
+#include "colmatch.h"
+#include "pragmas.h"
+#include "mmulti.h"
+#include "osd.h"
+#include "common_game.h"
+
+#include "aihand.h"
+#include "blood.h"
+#include "choke.h"
+#include "config.h"
+#include "db.h"
+#include "endgame.h"
+#include "gamemenu.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "map2d.h"
+#include "messages.h"
+#include "menu.h"
+#include "mirrors.h"
+#include "network.h"
+#include "player.h"
+#include "replace.h"
+#include "screen.h"
+#include "sectorfx.h"
+#include "tile.h"
+#include "trig.h"
+#include "view.h"
+#include "warp.h"
+#include "weapon.h"
+
+struct VIEW {
+ int at0;
+ int at4;
+ int at8; // bob height
+ int atc; // bob width
+ int at10;
+ int at14;
+ int at18; // bob sway y
+ int at1c; // bob sway x
+ fix16_t at20;
+ fix16_t at24; // horiz
+ int at28; // horizoff
+ int at2c;
+ fix16_t at30; // angle
+ int at34; // weapon z
+ int at38; // view z
+ int at3c;
+ int at40;
+ int at44;
+ int at48; // posture
+ int at4c; // spin
+ int at50; // x
+ int at54; // y
+ int at58; // z
+ int at5c; //xvel
+ int at60; //yvel
+ int at64; //zvel
+ short at68; // sectnum
+ unsigned int at6a; // floordist
+ char at6e; // look center
+ char at6f;
+ char at70; // run
+ char at71; // jump
+ char at72; // underwater
+ short at73; // sprite flags
+ SPRITEHIT at75;
+};
+
+VIEW gPrevView[kMaxPlayers];
+VIEWPOS gViewPos;
+int gViewIndex;
+
+struct INTERPOLATE {
+ void *pointer;
+ int value;
+ int value2;
+ INTERPOLATE_TYPE type;
+};
+
+int pcBackground;
+int gViewMode = 3;
+int gViewSize = 2;
+
+VIEW predict, predictOld;
+
+VIEW predictFifo[256];
+
+int gInterpolate;
+int nInterpolations;
+char gInterpolateSprite[512];
+char gInterpolateWall[1024];
+char gInterpolateSector[128];
+
+INTERPOLATE gInterpolation[4096];
+
+int gViewXCenter, gViewYCenter;
+int gViewX0, gViewY0, gViewX1, gViewY1;
+int gViewX0S, gViewY0S, gViewX1S, gViewY1S;
+int xscale, xscalecorrect, yscale, xstep, ystep;
+
+int gScreenTilt;
+
+CGameMessageMgr gGameMessageMgr;
+
+bool bLoadScreenCrcMatch = false;
+
+void RotateYZ(int *pX, int *pY, int *pZ, int ang)
+{
+ UNREFERENCED_PARAMETER(pX);
+ int oY, oZ, angSin, angCos;
+ oY = *pY;
+ oZ = *pZ;
+ angSin = Sin(ang);
+ angCos = Cos(ang);
+ *pY = dmulscale30r(oY,angCos,oZ,-angSin);
+ *pZ = dmulscale30r(oY,angSin,oZ,angCos);
+}
+
+void RotateXZ(int *pX, int *pY, int *pZ, int ang)
+{
+ UNREFERENCED_PARAMETER(pY);
+ int oX, oZ, angSin, angCos;
+ oX = *pX;
+ oZ = *pZ;
+ angSin = Sin(ang);
+ angCos = Cos(ang);
+ *pX = dmulscale30r(oX,angCos,oZ,-angSin);
+ *pZ = dmulscale30r(oX,angSin,oZ,angCos);
+}
+
+void RotateXY(int *pX, int *pY, int *pZ, int ang)
+{
+ UNREFERENCED_PARAMETER(pZ);
+ int oX, oY, angSin, angCos;
+ oX = *pX;
+ oY = *pY;
+ angSin = Sin(ang);
+ angCos = Cos(ang);
+ *pX = dmulscale30r(oX,angCos,oY,-angSin);
+ *pY = dmulscale30r(oX,angSin,oY,angCos);
+}
+
+FONT gFont[5];
+
+void FontSet(int id, int tile, int space)
+{
+ if (id < 0 || id >= 5 || tile < 0 || tile >= kMaxTiles)
+ return;
+
+ FONT *pFont = &gFont[id];
+ int xSize = 0;
+ int ySize = 0;
+ pFont->tile = tile;
+ for (int i = 0; i < 96; i++)
+ {
+ if (tilesiz[tile+i].x > xSize)
+ xSize = tilesiz[tile+i].x;
+ if (tilesiz[tile+i].y > ySize)
+ ySize = tilesiz[tile+i].y;
+ }
+ pFont->xSize = xSize;
+ pFont->ySize = ySize;
+ pFont->space = space;
+}
+
+void viewGetFontInfo(int id, const char *unk1, int *pXSize, int *pYSize)
+{
+ if (id < 0 || id >= 5)
+ return;
+ FONT *pFont = &gFont[id];
+ if (!unk1)
+ {
+ if (pXSize)
+ *pXSize = pFont->xSize;
+ if (pYSize)
+ *pYSize = pFont->ySize;
+ }
+ else
+ {
+ int width = -pFont->space;
+ for (const char *pBuf = unk1; *pBuf != 0; pBuf++)
+ {
+ int tile = ((*pBuf-32)&127)+pFont->tile;
+ if (tilesiz[tile].x != 0 && tilesiz[tile].y != 0)
+ width += tilesiz[tile].x+pFont->space;
+ }
+ if (pXSize)
+ *pXSize = width;
+ if (pYSize)
+ *pYSize = pFont->ySize;
+ }
+}
+
+void viewUpdatePages(void)
+{
+ pcBackground = numpages;
+}
+
+void viewToggle(int viewMode)
+{
+ if (viewMode == 3)
+ gViewMode = 4;
+ else
+ {
+ gViewMode = 3;
+ viewResizeView(gViewSize);
+ }
+}
+
+void viewInitializePrediction(void)
+{
+ predict.at30 = gMe->q16ang;
+ predict.at20 = gMe->q16look;
+ predict.at24 = gMe->q16horiz;
+ predict.at28 = gMe->q16slopehoriz;
+ predict.at2c = gMe->at83;
+ predict.at6f = gMe->at31c;
+ predict.at70 = gMe->at2e;
+ predict.at72 = gMe->at87;
+ predict.at71 = gMe->atc.buttonFlags.jump;
+ predict.at50 = gMe->pSprite->x;
+ predict.at54 = gMe->pSprite->y;
+ predict.at58 = gMe->pSprite->z;
+ predict.at68 = gMe->pSprite->sectnum;
+ predict.at73 = gMe->pSprite->hitag;
+ predict.at5c = xvel[gMe->pSprite->index];
+ predict.at60 = yvel[gMe->pSprite->index];
+ predict.at64 = zvel[gMe->pSprite->index];
+ predict.at6a = gMe->pXSprite->height;
+ predict.at48 = gMe->at2f;
+ predict.at4c = gMe->at316;
+ predict.at6e = gMe->atc.keyFlags.lookCenter;
+ memcpy(&predict.at75,&gSpriteHit[gMe->pSprite->extra],sizeof(SPRITEHIT));
+ predict.at0 = gMe->at37;
+ predict.at4 = gMe->at3b;
+ predict.at8 = gMe->at3f;
+ predict.atc = gMe->at43;
+ predict.at10 = gMe->at47;
+ predict.at14 = gMe->at4b;
+ predict.at18 = gMe->at4f;
+ predict.at1c = gMe->at53;
+ predict.at34 = gMe->at6f-gMe->at67-(12<<8);
+ predict.at38 = gMe->at67;
+ predict.at3c = gMe->at6b;
+ predict.at40 = gMe->at6f;
+ predict.at44 = gMe->at73;
+ predictOld = predict;
+}
+
+void viewUpdatePrediction(GINPUT *pInput)
+{
+ predictOld = predict;
+ short bakCstat = gMe->pSprite->cstat;
+ gMe->pSprite->cstat = 0;
+ fakePlayerProcess(gMe, pInput);
+ fakeActProcessSprites();
+ gMe->pSprite->cstat = bakCstat;
+ predictFifo[gPredictTail&255] = predict;
+ gPredictTail++;
+}
+
+void sub_158B4(PLAYER *pPlayer)
+{
+ predict.at38 = predict.at58 - gPosture[pPlayer->at5f][predict.at48].at24;
+ predict.at40 = predict.at58 - gPosture[pPlayer->at5f][predict.at48].at28;
+}
+
+void fakeProcessInput(PLAYER *pPlayer, GINPUT *pInput)
+{
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][predict.at48];
+ predict.at70 = pInput->syncFlags.run;
+ predict.at70 = 0;
+ predict.at71 = pInput->buttonFlags.jump;
+ if (predict.at48 == 1)
+ {
+ int x = Cos(fix16_to_int(predict.at30));
+ int y = Sin(fix16_to_int(predict.at30));
+ if (pInput->forward)
+ {
+ int forward = pInput->forward;
+ if (forward > 0)
+ forward = mulscale8(pPosture->at0, forward);
+ else
+ forward = mulscale8(pPosture->at8, forward);
+ predict.at5c += mulscale30(forward, x);
+ predict.at60 += mulscale30(forward, y);
+ }
+ if (pInput->strafe)
+ {
+ int strafe = pInput->strafe;
+ strafe = mulscale8(pPosture->at4, strafe);
+ predict.at5c += mulscale30(strafe, y);
+ predict.at60 -= mulscale30(strafe, x);
+ }
+ }
+ else if (predict.at6a < 0x100)
+ {
+ int speed = 0x10000;
+ if (predict.at6a > 0)
+ speed -= divscale16(predict.at6a, 0x100);
+ int x = Cos(fix16_to_int(predict.at30));
+ int y = Sin(fix16_to_int(predict.at30));
+ if (pInput->forward)
+ {
+ int forward = pInput->forward;
+ if (forward > 0)
+ forward = mulscale8(pPosture->at0, forward);
+ else
+ forward = mulscale8(pPosture->at8, forward);
+ if (predict.at6a)
+ forward = mulscale16(forward, speed);
+ predict.at5c += mulscale30(forward, x);
+ predict.at60 += mulscale30(forward, y);
+ }
+ if (pInput->strafe)
+ {
+ int strafe = pInput->strafe;
+ strafe = mulscale8(pPosture->at4, strafe);
+ if (predict.at6a)
+ strafe = mulscale16(strafe, speed);
+ predict.at5c += mulscale30(strafe, y);
+ predict.at60 -= mulscale30(strafe, x);
+ }
+ }
+ if (pInput->q16turn)
+ predict.at30 = (predict.at30+pInput->q16turn)&0x7ffffff;
+ if (pInput->keyFlags.spin180)
+ if (!predict.at4c)
+ predict.at4c = -1024;
+ if (predict.at4c < 0)
+ {
+ int speed;
+ if (predict.at48 == 1)
+ speed = 64;
+ else
+ speed = 128;
+
+ predict.at4c = ClipLow(predict.at4c+speed, 0);
+ predict.at30 += fix16_from_int(speed);
+ }
+
+ if (!predict.at71)
+ predict.at6f = 0;
+
+ switch (predict.at48)
+ {
+ case 1:
+ if (predict.at71)
+ predict.at64 -= 0x5b05;
+ if (pInput->buttonFlags.crouch)
+ predict.at64 += 0x5b05;
+ break;
+ case 2:
+ if (!pInput->buttonFlags.crouch)
+ predict.at48 = 0;
+ break;
+ default:
+ if (!predict.at6f && predict.at71 && predict.at6a == 0)
+ {
+ if (packItemActive(pPlayer, 4))
+ predict.at64 = -0x175555;
+ else
+ predict.at64 = -0xbaaaa;
+ predict.at6f = 1;
+ }
+ if (pInput->buttonFlags.crouch)
+ predict.at48 = 2;
+ break;
+ }
+#if 0
+ if (predict.at6e && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown)
+ {
+ if (predict.at20 < 0)
+ predict.at20 = fix16_min(predict.at20+F16(4), F16(0));
+ if (predict.at20 > 0)
+ predict.at20 = fix16_max(predict.at20-F16(4), F16(0));
+ if (predict.at20 == 0)
+ predict.at6e = 0;
+ }
+ else
+ {
+ if (pInput->buttonFlags.lookUp)
+ predict.at20 = fix16_min(predict.at20+F16(4), F16(60));
+ if (pInput->buttonFlags.lookDown)
+ predict.at20 = fix16_max(predict.at20-F16(4), F16(-60));
+ }
+ predict.at20 = fix16_clamp(predict.at20+pInput->q16mlook, F16(-60), F16(60));
+
+ if (predict.at20 > 0)
+ predict.at24 = mulscale30(F16(120), Sin(fix16_to_int(predict.at20<<3)));
+ else if (predict.at20 < 0)
+ predict.at24 = mulscale30(F16(180), Sin(fix16_to_int(predict.at20<<3)));
+ else
+ predict.at24 = 0;
+#endif
+ CONSTEXPR int upAngle = 289;
+ CONSTEXPR int downAngle = -347;
+ CONSTEXPR double lookStepUp = 4.0*upAngle/60.0;
+ CONSTEXPR double lookStepDown = -4.0*downAngle/60.0;
+ if (predict.at6e && !pInput->buttonFlags.lookUp && !pInput->buttonFlags.lookDown)
+ {
+ if (predict.at20 < 0)
+ predict.at20 = fix16_min(predict.at20+F16(lookStepDown), F16(0));
+ if (predict.at20 > 0)
+ predict.at20 = fix16_max(predict.at20-F16(lookStepUp), F16(0));
+ if (predict.at20 == 0)
+ predict.at6e = 0;
+ }
+ else
+ {
+ if (pInput->buttonFlags.lookUp)
+ predict.at20 = fix16_min(predict.at20+F16(lookStepUp), F16(upAngle));
+ if (pInput->buttonFlags.lookDown)
+ predict.at20 = fix16_max(predict.at20-F16(lookStepDown), F16(downAngle));
+ }
+ predict.at20 = fix16_clamp(predict.at20+(pInput->q16mlook<<3), F16(downAngle), F16(upAngle));
+ predict.at24 = fix16_from_float(100.f*tanf(fix16_to_float(predict.at20)*fPI/1024.f));
+
+ int nSector = predict.at68;
+ int florhit = predict.at75.florhit & 0xe000;
+ char va;
+ if (predict.at6a < 16 && (florhit == 0x4000 || florhit == 0))
+ va = 1;
+ else
+ va = 0;
+ if (va && (sector[nSector].floorstat&2) != 0)
+ {
+ int z1 = getflorzofslope(nSector, predict.at50, predict.at54);
+ int x2 = predict.at50+mulscale30(64, Cos(fix16_to_int(predict.at30)));
+ int y2 = predict.at54+mulscale30(64, Sin(fix16_to_int(predict.at30)));
+ short nSector2 = nSector;
+ updatesector(x2, y2, &nSector2);
+ if (nSector2 == nSector)
+ {
+ int z2 = getflorzofslope(nSector2, x2, y2);
+ predict.at28 = interpolate(predict.at28, fix16_from_int(z1-z2)>>3, 0x4000);
+ }
+ }
+ else
+ {
+ predict.at28 = interpolate(predict.at28, 0, 0x4000);
+ if (klabs(predict.at28) < 4)
+ predict.at28 = 0;
+ }
+ predict.at2c = (-fix16_to_int(predict.at24))<<7;
+}
+
+void fakePlayerProcess(PLAYER *pPlayer, GINPUT *pInput)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][predict.at48];
+
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+
+ top += predict.at58-pSprite->z;
+ bottom += predict.at58-pSprite->z;
+
+ int dzb = (bottom-predict.at58)/4;
+ int dzt = (predict.at58-top)/4;
+
+ int dw = pSprite->clipdist<<2;
+ short nSector = predict.at68;
+ if (!gNoClip)
+ {
+ pushmove_old((int32_t*)&predict.at50, (int32_t*)&predict.at54, (int32_t*)&predict.at58, &predict.at68, dw, dzt, dzb, CLIPMASK0);
+ if (predict.at68 == -1)
+ predict.at68 = nSector;
+ }
+ fakeProcessInput(pPlayer, pInput);
+
+ int nSpeed = approxDist(predict.at5c, predict.at60);
+
+ predict.at3c = interpolate(predict.at3c, predict.at64, 0x7000);
+ int dz = predict.at58-pPosture->at24-predict.at38;
+ if (dz > 0)
+ predict.at3c += mulscale16(dz<<8, 0xa000);
+ else
+ predict.at3c += mulscale16(dz<<8, 0x1800);
+ predict.at38 += predict.at3c>>8;
+
+ predict.at44 = interpolate(predict.at44, predict.at64, 0x5000);
+ dz = predict.at58-pPosture->at28-predict.at40;
+ if (dz > 0)
+ predict.at44 += mulscale16(dz<<8, 0x8000);
+ else
+ predict.at44 += mulscale16(dz<<8, 0xc00);
+ predict.at40 += predict.at44>>8;
+
+ predict.at34 = predict.at40 - predict.at38 - (12<<8);
+
+ predict.at0 = ClipLow(predict.at0-4, 0);
+
+ nSpeed >>= 16;
+ if (predict.at48 == 1)
+ {
+ predict.at4 = (predict.at4+17)&2047;
+ predict.at14 = (predict.at14+17)&2047;
+ predict.at8 = mulscale30(10*pPosture->at14,Sin(predict.at4*2));
+ predict.atc = mulscale30(predict.at0*pPosture->at18,Sin(predict.at4-256));
+ predict.at18 = mulscale30(predict.at0*pPosture->at1c,Sin(predict.at14*2));
+ predict.at1c = mulscale30(predict.at0*pPosture->at20,Sin(predict.at14-0x155));
+ }
+ else
+ {
+ if (pXSprite->height < 256)
+ {
+ predict.at4 = (predict.at4+(pPosture->atc[predict.at70]*4))&2047;
+ predict.at14 = (predict.at14+(pPosture->atc[predict.at70]*4)/2)&2047;
+ if (predict.at70)
+ {
+ if (predict.at0 < 60)
+ predict.at0 = ClipHigh(predict.at0 + nSpeed, 60);
+ }
+ else
+ {
+ if (predict.at0 < 30)
+ predict.at0 = ClipHigh(predict.at0 + nSpeed, 30);
+ }
+ }
+ predict.at8 = mulscale30(predict.at0*pPosture->at14,Sin(predict.at4*2));
+ predict.atc = mulscale30(predict.at0*pPosture->at18,Sin(predict.at4-256));
+ predict.at18 = mulscale30(predict.at0*pPosture->at1c,Sin(predict.at14*2));
+ predict.at1c = mulscale30(predict.at0*pPosture->at20,Sin(predict.at14-0x155));
+ }
+ if (!pXSprite->health)
+ return;
+ predict.at72 = 0;
+ if (predict.at48 == 1)
+ {
+ predict.at72 = 1;
+ int nSector = predict.at68;
+ int nLink = gLowerLink[nSector];
+ if (nLink > 0 && (sprite[nLink].type == 14 || sprite[nLink].type == 10))
+ {
+ if (getceilzofslope(nSector, predict.at50, predict.at54) > predict.at38)
+ predict.at72 = 0;
+ }
+ }
+}
+
+void fakeMoveDude(spritetype *pSprite)
+{
+ PLAYER *pPlayer = NULL;
+ int bottom, top;
+ if (IsPlayerSprite(pSprite))
+ pPlayer = &gPlayer[pSprite->type-kDudePlayer1];
+ dassert(pSprite->type >= kDudeBase && pSprite->type < kDudeMax);
+ GetSpriteExtents(pSprite, &top, &bottom);
+ top += predict.at58 - pSprite->z;
+ bottom += predict.at58 - pSprite->z;
+ int bz = (bottom-predict.at58)/4;
+ int tz = (predict.at58-top)/4;
+ int wd = pSprite->clipdist*4;
+ int nSector = predict.at68;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ if (predict.at5c || predict.at60)
+ {
+ if (pPlayer && gNoClip)
+ {
+ predict.at50 += predict.at5c>>12;
+ predict.at54 += predict.at60>>12;
+ if (!FindSector(predict.at50, predict.at54, &nSector))
+ nSector = predict.at68;
+ }
+ else
+ {
+ short bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~257;
+ predict.at75.hit = ClipMove(&predict.at50, &predict.at54, &predict.at58, &nSector, predict.at5c >> 12, predict.at60 >> 12, wd, tz, bz, 0x13001);
+ if (nSector == -1)
+ nSector = predict.at68;
+
+ if (sector[nSector].lotag >= 612 && sector[nSector].lotag <= 617)
+ {
+ short nSector2 = nSector;
+ pushmove_old((int32_t*)&predict.at50, (int32_t*)&predict.at54, (int32_t*)&predict.at58, &nSector2, wd, tz, bz, 0x10001);
+ if (nSector2 != -1)
+ nSector = nSector2;
+ }
+
+ dassert(nSector >= 0);
+
+ pSprite->cstat = bakCstat;
+ }
+ switch (predict.at75.hit&0xe000)
+ {
+ case 0x8000:
+ {
+ int nHitWall = predict.at75.hit&0x1fff;
+ walltype *pHitWall = &wall[nHitWall];
+ if (pHitWall->nextsector != -1)
+ {
+ sectortype *pHitSector = §or[pHitWall->nextsector];
+ if (top < pHitSector->ceilingz || bottom > pHitSector->floorz)
+ {
+ // ???
+ }
+ }
+ actWallBounceVector(&predict.at5c, &predict.at60, nHitWall, 0);
+ break;
+ }
+ }
+ }
+ if (predict.at68 != nSector)
+ {
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ predict.at68 = nSector;
+ }
+ char bUnderwater = 0;
+ char bDepth = 0;
+ int nXSector = sector[nSector].extra;
+ if (nXSector > 0)
+ {
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (pXSector->Underwater)
+ bUnderwater = 1;
+ if (pXSector->Depth)
+ bDepth = 1;
+ }
+ int nUpperLink = gUpperLink[nSector];
+ int nLowerLink = gLowerLink[nSector];
+ if (nUpperLink >= 0 && (sprite[nUpperLink].type == 9 || sprite[nUpperLink].type == 13))
+ bDepth = 1;
+ if (nLowerLink >= 0 && (sprite[nLowerLink].type == 10 || sprite[nLowerLink].type == 14))
+ bDepth = 1;
+ if (pPlayer)
+ wd += 16;
+
+ if (predict.at64)
+ predict.at58 += predict.at64 >> 8;
+
+ spritetype pSpriteBak = *pSprite;
+ spritetype *pTempSprite = pSprite;
+ pTempSprite->x = predict.at50;
+ pTempSprite->y = predict.at54;
+ pTempSprite->z = predict.at58;
+ pTempSprite->sectnum = predict.at68;
+ int ceilZ, ceilHit, floorZ, floorHit;
+ GetZRange(pTempSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, wd, 0x10001);
+ GetSpriteExtents(pTempSprite, &top, &bottom);
+ if (predict.at73 & 2)
+ {
+ int vc = 58254;
+ if (bDepth)
+ {
+ if (bUnderwater)
+ {
+ int cz = getceilzofslope(nSector, predict.at50, predict.at54);
+ if (cz > top)
+ vc += ((bottom-cz)*-80099) / (bottom-top);
+ else
+ vc = 0;
+ }
+ else
+ {
+ int fz = getflorzofslope(nSector, predict.at50, predict.at54);
+ if (fz < bottom)
+ vc += ((bottom-fz)*-80099) / (bottom-top);
+ }
+ }
+ else
+ {
+ if (bUnderwater)
+ vc = 0;
+ else if (bottom >= floorZ)
+ vc = 0;
+ }
+ if (vc)
+ {
+ predict.at58 += ((vc*4)/2)>>8;
+ predict.at64 += vc;
+ }
+ }
+ GetSpriteExtents(pTempSprite, &top, &bottom);
+ if (bottom >= floorZ)
+ {
+ int floorZ2 = floorZ;
+ int floorHit2 = floorHit;
+ GetZRange(pTempSprite, &ceilZ, &ceilHit, &floorZ, &floorHit, pSprite->clipdist<<2, 0x13001);
+ if (bottom <= floorZ && predict.at58-floorZ2 < bz)
+ {
+ floorZ = floorZ2;
+ floorHit = floorHit2;
+ }
+ }
+ if (floorZ <= bottom)
+ {
+ predict.at75.florhit = floorHit;
+ predict.at58 += floorZ-bottom;
+ int var44 = predict.at64-velFloor[predict.at68];
+ if (var44 > 0)
+ {
+ actFloorBounceVector(&predict.at5c, &predict.at60, &var44, predict.at68, 0);
+ predict.at64 = var44;
+ if (klabs(predict.at64) < 0x10000)
+ {
+ predict.at64 = velFloor[predict.at68];
+ predict.at73 &= ~4;
+ }
+ else
+ predict.at73 |= 4;
+ }
+ else if (predict.at64 == 0)
+ predict.at73 &= ~4;
+ }
+ else
+ {
+ predict.at75.florhit = 0;
+ if (predict.at73 & 2)
+ predict.at73 |= 4;
+ }
+ if (top <= ceilZ)
+ {
+ predict.at75.ceilhit = ceilHit;
+ predict.at58 += ClipLow(ceilZ-top, 0);
+ if (predict.at64 <= 0 && (predict.at73&4))
+ predict.at64 = mulscale16(-predict.at64, 0x2000);
+ }
+ else
+ predict.at75.ceilhit = 0;
+
+ GetSpriteExtents(pTempSprite, &top, &bottom);
+ *pSprite = pSpriteBak;
+ predict.at6a = ClipLow(floorZ-bottom, 0)>>8;
+ if (predict.at5c || predict.at60)
+ {
+ if ((floorHit & 0xe000) == 0xc000)
+ {
+ int nHitSprite = floorHit & 0x1fff;
+ if ((sprite[nHitSprite].cstat & 0x30) == 0)
+ {
+ predict.at5c += mulscale(4, predict.at50 - sprite[nHitSprite].x, 2);
+ predict.at60 += mulscale(4, predict.at54 - sprite[nHitSprite].y, 2);
+ return;
+ }
+ }
+ int nXSector = sector[pSprite->sectnum].extra;
+ if (nXSector > 0 && xsector[nXSector].Underwater)
+ return;
+ if (predict.at6a >= 0x100)
+ return;
+ int nDrag = gDudeDrag;
+ if (predict.at6a > 0)
+ nDrag -= scale(gDudeDrag, predict.at6a, 0x100);
+ predict.at5c -= mulscale16r(predict.at5c, nDrag);
+ predict.at60 -= mulscale16r(predict.at60, nDrag);
+ if (approxDist(predict.at5c, predict.at60) < 0x1000)
+ predict.at5c = predict.at60 = 0;
+ }
+}
+
+void fakeActAirDrag(spritetype *pSprite, int num)
+{
+ UNREFERENCED_PARAMETER(pSprite);
+ int xvec = 0;
+ int yvec = 0;
+ int nSector = predict.at68;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ sectortype *pSector = §or[nSector];
+ int nXSector = pSector->extra;
+ if (nXSector > 0)
+ {
+ dassert(nXSector < kMaxXSectors);
+ XSECTOR *pXSector = &xsector[nXSector];
+ if (pXSector->windVel && (pXSector->windAlways || pXSector->busy))
+ {
+ int vel = pXSector->windVel<<12;
+ if (!pXSector->windAlways && pXSector->busy)
+ vel = mulscale16(vel, pXSector->busy);
+ xvec = mulscale30(vel, Cos(pXSector->windAng));
+ yvec = mulscale30(vel, Sin(pXSector->windAng));
+ }
+ }
+ predict.at5c += mulscale16(xvec-predict.at5c, num);
+ predict.at60 += mulscale16(yvec-predict.at60, num);
+ predict.at64 -= mulscale16(predict.at64, num);
+}
+
+void fakeActProcessSprites(void)
+{
+ spritetype *pSprite = gMe->pSprite;
+ if (pSprite->statnum == 6)
+ {
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ int nSector = predict.at68;
+ int nXSector = sector[nSector].extra;
+ XSECTOR *pXSector = NULL;
+ if (nXSector > 0)
+ {
+ dassert(nXSector > 0 && nXSector < kMaxXSectors);
+ dassert(xsector[nXSector].reference == nSector);
+ pXSector = &xsector[nXSector];
+ }
+ if (pXSector)
+ {
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ top += predict.at58 - pSprite->z;
+ bottom += predict.at58 - pSprite->z;
+ if (getflorzofslope(nSector, predict.at50, predict.at54) < bottom)
+ {
+ int angle = pXSector->panAngle;
+ int speed = 0;
+ if (pXSector->panAlways || pXSector->state || pXSector->busy)
+ {
+ speed = pXSector->panVel << 9;
+ if (!pXSector->panAlways && pXSector->busy)
+ speed = mulscale16(speed, pXSector->busy);
+ }
+ if (sector[nSector].floorstat&64)
+ angle = (GetWallAngle(sector[nSector].wallptr)+512)&2047;
+ predict.at5c += mulscale30(speed,Cos(angle));
+ predict.at60 += mulscale30(speed,Sin(angle));
+ }
+ }
+ if (pXSector && pXSector->Underwater)
+ fakeActAirDrag(pSprite, 5376);
+ else
+ fakeActAirDrag(pSprite, 128);
+
+ if ((predict.at73 & 4) != 0 || predict.at5c != 0 || predict.at60 != 0 || predict.at64 != 0 || velFloor[predict.at68] != 0 || velCeil[predict.at68] != 0)
+ {
+ fakeMoveDude(pSprite);
+ }
+ }
+}
+
+void viewCorrectPrediction(void)
+{
+ if (gGameOptions.nGameType == 0) return;
+ spritetype *pSprite = gMe->pSprite;
+ VIEW *pView = &predictFifo[(gNetFifoTail-1)&255];
+ if (gMe->q16ang != pView->at30 || pView->at24 != gMe->q16horiz || pView->at50 != pSprite->x || pView->at54 != pSprite->y || pView->at58 != pSprite->z)
+ {
+ viewInitializePrediction();
+ predictOld = gPrevView[myconnectindex];
+ gPredictTail = gNetFifoTail;
+ while (gPredictTail < gNetFifoHead[myconnectindex])
+ {
+ viewUpdatePrediction(&gFifoInput[gPredictTail&255][myconnectindex]);
+ }
+ }
+}
+
+void viewBackupView(int nPlayer)
+{
+ PLAYER *pPlayer = &gPlayer[nPlayer];
+ VIEW *pView = &gPrevView[nPlayer];
+ pView->at30 = pPlayer->q16ang;
+ pView->at50 = pPlayer->pSprite->x;
+ pView->at54 = pPlayer->pSprite->y;
+ pView->at38 = pPlayer->at67;
+ pView->at34 = pPlayer->at6f-pPlayer->at67-0xc00;
+ pView->at24 = pPlayer->q16horiz;
+ pView->at28 = pPlayer->q16slopehoriz;
+ pView->at2c = pPlayer->at83;
+ pView->at8 = pPlayer->at3f;
+ pView->atc = pPlayer->at43;
+ pView->at18 = pPlayer->at4f;
+ pView->at1c = pPlayer->at53;
+}
+
+void viewClearInterpolations(void)
+{
+ nInterpolations = 0;
+ memset(gInterpolateSprite, 0, sizeof(gInterpolateSprite));
+ memset(gInterpolateWall, 0, sizeof(gInterpolateWall));
+ memset(gInterpolateSector, 0, sizeof(gInterpolateSector));
+}
+
+void viewAddInterpolation(void *data, INTERPOLATE_TYPE type)
+{
+ if (nInterpolations == 4096)
+ ThrowError("Too many interpolations");
+ INTERPOLATE *pInterpolate = &gInterpolation[nInterpolations++];
+ pInterpolate->pointer = data;
+ pInterpolate->type = type;
+ switch (type)
+ {
+ case INTERPOLATE_TYPE_INT:
+ pInterpolate->value = *((int*)data);
+ break;
+ case INTERPOLATE_TYPE_SHORT:
+ pInterpolate->value = *((short*)data);
+ break;
+ }
+}
+
+void CalcInterpolations(void)
+{
+ int i;
+ INTERPOLATE *pInterpolate = gInterpolation;
+ for (i = 0; i < nInterpolations; i++, pInterpolate++)
+ {
+ switch (pInterpolate->type)
+ {
+ case INTERPOLATE_TYPE_INT:
+ {
+ pInterpolate->value2 = *((int*)pInterpolate->pointer);
+ int newValue = interpolate(pInterpolate->value, *((int*)pInterpolate->pointer), gInterpolate);
+ *((int*)pInterpolate->pointer) = newValue;
+ break;
+ }
+ case INTERPOLATE_TYPE_SHORT:
+ {
+ pInterpolate->value2 = *((short*)pInterpolate->pointer);
+ int newValue = interpolate(pInterpolate->value, *((short*)pInterpolate->pointer), gInterpolate);
+ *((short*)pInterpolate->pointer) = newValue;
+ break;
+ }
+ }
+ }
+}
+
+void RestoreInterpolations(void)
+{
+ int i;
+ INTERPOLATE *pInterpolate = gInterpolation;
+ for (i = 0; i < nInterpolations; i++, pInterpolate++)
+ {
+ switch (pInterpolate->type)
+ {
+ case INTERPOLATE_TYPE_INT:
+ *((int*)pInterpolate->pointer) = pInterpolate->value2;
+ break;
+ case INTERPOLATE_TYPE_SHORT:
+ *((short*)pInterpolate->pointer) = pInterpolate->value2;
+ break;
+ }
+ }
+}
+
+void viewDrawText(int nFont, const char *pString, int x, int y, int nShade, int nPalette, int position, char shadow, unsigned int nStat, uint8_t alpha)
+{
+ if (nFont < 0 || nFont >= 5 || !pString) return;
+ FONT *pFont = &gFont[nFont];
+
+ if (position)
+ {
+ const char *s = pString;
+ int width = -pFont->space;
+ while (*s)
+ {
+ int nTile = ((*s-' ')&127)+pFont->tile;
+ if (tilesiz[nTile].x && tilesiz[nTile].y)
+ width += tilesiz[nTile].x+pFont->space;
+ s++;
+ }
+ if (position == 1)
+ width >>= 1;
+ x -= width;
+ }
+ const char *s = pString;
+ while (*s)
+ {
+ int nTile = ((*s-' ')&127) + pFont->tile;
+ if (tilesiz[nTile].x && tilesiz[nTile].y)
+ {
+ if (shadow)
+ {
+ rotatesprite_fs_alpha((x+1)<<16, (y+1)<<16, 65536, 0, nTile, 127, nPalette, 26|nStat, alpha);
+ }
+ rotatesprite_fs_alpha(x<<16, y<<16, 65536, 0, nTile, nShade, nPalette, 26|nStat, alpha);
+ x += tilesiz[nTile].x+pFont->space;
+ }
+ s++;
+ }
+}
+
+void viewTileSprite(int nTile, int nShade, int nPalette, int x1, int y1, int x2, int y2)
+{
+ Rect rect1 = Rect(x1, y1, x2, y2);
+ Rect rect2 = Rect(0, 0, xdim, ydim);
+ rect1 &= rect2;
+
+ if (!rect1)
+ return;
+
+ dassert(nTile >= 0 && nTile < kMaxTiles);
+ int width = tilesiz[nTile].x;
+ int height = tilesiz[nTile].y;
+ int bx1 = DecBy(rect1.x1+1, width);
+ int by1 = DecBy(rect1.y1+1, height);
+ int bx2 = IncBy(rect1.x2-1, width);
+ int by2 = IncBy(rect1.y2-1, height);
+ for (int x = bx1; x < bx2; x += width)
+ for (int y = by1; y < by2; y += height)
+ rotatesprite(x<<16, y<<16, 65536, 0, nTile, nShade, nPalette, 64+16+8, x1, y1, x2-1, y2-1);
+}
+
+void InitStatusBar(void)
+{
+ tileLoadTile(2200);
+}
+void DrawStatSprite(int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat, int nScale)
+{
+ rotatesprite(x<<16, y<<16, nScale, 0, nTile, nShade, nPalette, nStat | 74, 0, 0, xdim-1, ydim-1);
+}
+void DrawStatMaskedSprite(int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat, int nScale)
+{
+ rotatesprite(x<<16, y<<16, nScale, 0, nTile, nShade, nPalette, nStat | 10, 0, 0, xdim-1, ydim-1);
+}
+
+void DrawStatNumber(const char *pFormat, int nNumber, int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat, int nScale)
+{
+ char tempbuf[80];
+ int width = tilesiz[nTile].x+1;
+ x <<= 16;
+ sprintf(tempbuf, pFormat, nNumber);
+ for (unsigned int i = 0; i < strlen(tempbuf); i++, x += width*nScale)
+ {
+ if (tempbuf[i] == ' ') continue;
+ rotatesprite(x, y<<16, nScale, 0, nTile+tempbuf[i]-'0', nShade, nPalette, nStat | 10, 0, 0, xdim-1, ydim-1);
+ }
+}
+
+void TileHGauge(int nTile, int x, int y, int nMult, int nDiv, int nStat, int nScale)
+{
+ int bx = scale(mulscale16(tilesiz[nTile].x,nScale),nMult,nDiv)+x;
+ int sbx;
+ switch (nStat&(512+256))
+ {
+ case 256:
+ sbx = mulscale16(bx, xscalecorrect)-1;
+ break;
+ case 512:
+ bx -= 320;
+ sbx = xdim+mulscale16(bx, xscalecorrect)-1;
+ break;
+ default:
+ bx -= 160;
+ sbx = (xdim>>1)+mulscale16(bx, xscalecorrect)-1;
+ break;
+ }
+ rotatesprite(x<<16, y<<16, nScale, 0, nTile, 0, 0, nStat|90, 0, 0, sbx, ydim-1);
+}
+
+int gPackIcons[5] = {
+ 2569, 2564, 2566, 2568, 2560
+};
+
+struct PACKICON2 {
+ short nTile;
+ int nScale;
+ int nYOffs;
+};
+
+PACKICON2 gPackIcons2[] = {
+ { 519, (int)(65536*0.5), 0 },
+ { 830, (int)(65536*0.3), 0 },
+ { 760, (int)(65536*0.6), 0 },
+ { 839, (int)(65536*0.5), -4 },
+ { 827, (int)(65536*0.4), 0 },
+};
+
+struct AMMOICON {
+ short nTile;
+ int nScale;
+ int nYOffs;
+};
+
+AMMOICON gAmmoIcons[] = {
+ { -1, 0, 0 },
+ { 816, (int)(65536 * 0.5), 0 },
+ { 619, (int)(65536 * 0.8), 0 },
+ { 817, (int)(65536 * 0.7), 3 },
+ { 801, (int)(65536 * 0.5), -6 },
+ { 589, (int)(65536 * 0.7), 2 },
+ { 618, (int)(65536 * 0.5), 4 },
+ { 548, (int)(65536 * 0.3), -6 },
+ { 820, (int)(65536 * 0.3), -6 },
+ { 525, (int)(65536 * 0.6), -6 },
+ { 811, (int)(65536 * 0.5), 2 },
+ { 810, (int)(65536 * 0.45), 2 },
+};
+
+struct WEAPONICON {
+ short nTile;
+ char xRepeat;
+ char yRepeat;
+};
+
+WEAPONICON gWeaponIcon[] = {
+ { -1, 0, 0 },
+ { -1, 0, 0 },
+ { 524, 32, 32 },
+ { 559, 32, 32 },
+ { 558, 32, 32 },
+ { 526, 32, 32 },
+ { 589, 32, 32 },
+ { 618, 32, 32 },
+ { 539, 32, 32 },
+ { 800, 32, 32 },
+ { 525, 32, 32 },
+ { 811, 32, 32 },
+ { 810, 32, 32 },
+ { -1, 0, 0 },
+};
+
+int dword_14C508;
+
+void viewDrawStats(PLAYER *pPlayer, int x, int y)
+{
+ const int nFont = 3;
+ char buffer[128];
+ if (!gLevelStats)
+ return;
+
+ int nHeight;
+ viewGetFontInfo(nFont, NULL, NULL, &nHeight);
+ sprintf(buffer, "T:%d:%02d.%02d",
+ (gLevelTime/(kTicsPerSec*60)),
+ (gLevelTime/kTicsPerSec)%60,
+ ((gLevelTime%kTicsPerSec)*33)/10
+ );
+ viewDrawText(3, buffer, x, y, 20, 0, 0, true, 256);
+ y += nHeight+1;
+ if (gGameOptions.nGameType == 0 || gGameOptions.nGameType == 2)
+ sprintf(buffer, "K:%d/%d", gKillMgr.at4, gKillMgr.at0);
+ else
+ sprintf(buffer, "K:%d", pPlayer->at2c6);
+ viewDrawText(3, buffer, x, y, 20, 0, 0, true, 256);
+ y += nHeight+1;
+ sprintf(buffer, "S:%d/%d", gSecretMgr.at4+gSecretMgr.at8, gSecretMgr.at0);
+ viewDrawText(3, buffer, x, y, 20, 0, 0, true, 256);
+}
+
+#define kSBarNumberHealth 9220
+#define kSBarNumberAmmo 9230
+#define kSBarNumberInv 9240
+#define kSBarNumberArmor1 9250
+#define kSBarNumberArmor2 9260
+#define kSBarNumberArmor3 9270
+
+struct POWERUPDISPLAY
+{
+ int nTile;
+ float nScaleRatio;
+ int yOffset;
+ int remainingDuration;
+};
+
+void sortPowerUps(POWERUPDISPLAY* powerups) {
+ for (int i = 1; i < 5; i++)
+ {
+ for (int j = 0; j < 5-i; j++)
+ {
+ if (powerups[j].remainingDuration > powerups[j+1].remainingDuration)
+ {
+ POWERUPDISPLAY temp = powerups[j];
+ powerups[j] = powerups[j+1];
+ powerups[j+1] = temp;
+ }
+ }
+ }
+}
+
+void viewDrawPowerUps(PLAYER* pPlayer)
+{
+ if (!gPowerupDuration)
+ return;
+
+ const int nCloakOfInvisibility = 13;
+ const int nReflectiveShots = 24;
+ const int nDeathMask = 14; // invulnerability
+ const int nGunsAkimbo = 17;
+ const int nCloakOfShadow = 26; // does nothing, only appears at near the end of Cryptic Passage's Lost Monastery (CP04)
+
+ POWERUPDISPLAY powerups[5];
+ powerups[0] = { 896, 0.4f, 0, pPlayer->at202[nCloakOfInvisibility] };
+ powerups[1] = { 2428, 0.4f, 5, pPlayer->at202[nReflectiveShots] };
+ powerups[2] = { 825, 0.3f, 9, pPlayer->at202[nDeathMask] };
+ powerups[3] = { 829, 0.3f, 5, pPlayer->at202[nGunsAkimbo] };
+ powerups[4] = { 768, 0.4f, 9, pPlayer->at202[nCloakOfShadow] };
+
+ sortPowerUps(powerups);
+
+ const int x = 15;
+ int y = 50;
+ for (int i = 0; i < 5; i++)
+ {
+ if (powerups[i].remainingDuration)
+ {
+ int remainingSeconds = powerups[i].remainingDuration / 100;
+ if (remainingSeconds > 5 || (gGameClock & 32))
+ {
+ DrawStatMaskedSprite(powerups[i].nTile, x, y + powerups[i].yOffset, 0, 0, 256, (int)(65536 * powerups[i].nScaleRatio));
+ }
+
+ DrawStatNumber("%d", remainingSeconds, kSBarNumberInv, x + 15, y, 0, 0, 256, 65536 * 0.5);
+ y += 20;
+ }
+ }
+}
+
+void viewDrawMapTitle(void)
+{
+ if (!gShowMapTitle || gGameMenuMgr.m_bActive)
+ return;
+
+ int seconds = (gLevelTime / kTicsPerSec);
+ int millisecs = (gLevelTime % kTicsPerSec) * 33;
+ if (seconds > 3)
+ return;
+
+ const int noAlphaForSecs = 1;
+ uint8_t alpha = videoGetRenderMode() != REND_CLASSIC || numalphatabs >= 15 ?
+ seconds < noAlphaForSecs ? 0 : clamp(((seconds-noAlphaForSecs)*1000+millisecs)/4, 0, 255)
+ : 0;
+
+ if (alpha != 255)
+ {
+ viewDrawText(1, levelGetTitle(), 160, 50, -128, 0, 1, 1, 0, alpha);
+ }
+}
+
+void viewDrawPack(PLAYER *pPlayer, int x, int y)
+{
+ int packs[5];
+ if (pPlayer->at31d)
+ {
+ int nPacks = 0;
+ int width = 0;
+ for (int i = 0; i < 5; i++)
+ {
+ if (pPlayer->packInfo[i].at1)
+ {
+ packs[nPacks++] = i;
+ width += tilesiz[gPackIcons[i]].x + 1;
+ }
+ }
+ width /= 2;
+ x -= width;
+ for (int i = 0; i < nPacks; i++)
+ {
+ int nPack = packs[i];
+ DrawStatSprite(2568, x+1, y-8);
+ DrawStatSprite(2568, x+1, y-6);
+ DrawStatSprite(gPackIcons[nPack], x+1, y+1);
+ if (nPack == pPlayer->at321)
+ DrawStatMaskedSprite(2559, x+1, y+1);
+ int nShade;
+ if (pPlayer->packInfo[nPack].at0)
+ nShade = 4;
+ else
+ nShade = 24;
+ DrawStatNumber("%3d", pPlayer->packInfo[nPack].at1, 2250, x-4, y-13, nShade, 0);
+ x += tilesiz[gPackIcons[nPack]].x + 1;
+ }
+ }
+ if (pPlayer->at31d != dword_14C508)
+ {
+ viewUpdatePages();
+ }
+ dword_14C508 = pPlayer->at31d;
+}
+
+void DrawPackItemInStatusBar(PLAYER *pPlayer, int x, int y, int x2, int y2, int nStat)
+{
+ if (pPlayer->at321 < 0) return;
+
+ DrawStatSprite(gPackIcons[pPlayer->at321], x, y, 0, 0, nStat);
+ DrawStatNumber("%3d", pPlayer->packInfo[pPlayer->at321].at1, 2250, x2, y2, 0, 0, nStat);
+}
+
+void DrawPackItemInStatusBar2(PLAYER *pPlayer, int x, int y, int x2, int y2, int nStat, int nScale)
+{
+ if (pPlayer->at321 < 0) return;
+
+ DrawStatMaskedSprite(gPackIcons2[pPlayer->at321].nTile, x, y+gPackIcons2[pPlayer->at321].nYOffs, 0, 0, nStat, gPackIcons2[pPlayer->at321].nScale);
+ DrawStatNumber("%3d", pPlayer->packInfo[pPlayer->at321].at1, kSBarNumberInv, x2, y2, 0, 0, nStat, nScale);
+}
+
+char gTempStr[128];
+
+void UpdateStatusBar(int arg)
+{
+ PLAYER *pPlayer = gView;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+
+ int nPalette = 0;
+
+ if (gGameOptions.nGameType == 3)
+ {
+ if (pPlayer->at2ea & 1)
+ nPalette = 7;
+ else
+ nPalette = 10;
+ }
+
+ if (gViewSize < 0) return;
+
+ if (gViewSize == 1)
+ {
+ DrawStatMaskedSprite(2169, 12, 195, 0, 0, 256, (int)(65536*0.56));
+ DrawStatNumber("%d", pXSprite->health>>4, kSBarNumberHealth, 28, 187, 0, 0, 256);
+ if (pPlayer->at33e[1])
+ {
+ DrawStatMaskedSprite(2578, 70, 186, 0, 0, 256, (int)(65536*0.5));
+ DrawStatNumber("%3d", pPlayer->at33e[1]>>4, kSBarNumberArmor2, 83, 187, 0, 0, 256, (int)(65536*0.65));
+ }
+ if (pPlayer->at33e[0])
+ {
+ DrawStatMaskedSprite(2586, 112, 195, 0, 0, 256, (int)(65536*0.5));
+ DrawStatNumber("%3d", pPlayer->at33e[0]>>4, kSBarNumberArmor1, 125, 187, 0, 0, 256, (int)(65536*0.65));
+ }
+ if (pPlayer->at33e[2])
+ {
+ DrawStatMaskedSprite(2602, 155, 196, 0, 0, 256, (int)(65536*0.5));
+ DrawStatNumber("%3d", pPlayer->at33e[2]>>4, kSBarNumberArmor3, 170, 187, 0, 0, 256, (int)(65536*0.65));
+ }
+
+ DrawPackItemInStatusBar2(pPlayer, 225, 194, 240, 187, 512, (int)(65536*0.7));
+
+ if (pPlayer->atbd && pPlayer->atc7 != -1)
+ {
+ int num = pPlayer->at181[pPlayer->atc7];
+ if (pPlayer->atc7 == 6)
+ num /= 10;
+ if ((unsigned int)gAmmoIcons[pPlayer->atc7].nTile < kMaxTiles)
+ DrawStatMaskedSprite(gAmmoIcons[pPlayer->atc7].nTile, 304, 192+gAmmoIcons[pPlayer->atc7].nYOffs,
+ 0, 0, 512, gAmmoIcons[pPlayer->atc7].nScale);
+ DrawStatNumber("%3d", num, kSBarNumberAmmo, 267, 187, 0, 0, 512);
+ }
+
+ for (int i = 0; i < 6; i++)
+ {
+ if (pPlayer->at88[i+1])
+ DrawStatMaskedSprite(2552+i, 260+10*i, 170, 0, 0, 512, (int)(65536*0.25));
+ }
+
+ if (pPlayer->at1ba)
+ TileHGauge(2260, 124, 175-10, pPlayer->at1ba, 65536);
+ else
+ viewDrawPack(pPlayer, 166, 200-tilesiz[2201].y/2-30);
+ viewDrawStats(pPlayer, 2, 140);
+ viewDrawPowerUps(pPlayer);
+ }
+ else if (gViewSize <= 2)
+ {
+ if (pPlayer->at1ba)
+ TileHGauge(2260, 124, 175, pPlayer->at1ba, 65536);
+ else
+ viewDrawPack(pPlayer, 166, 200-tilesiz[2201].y/2);
+ }
+ if (gViewSize == 2)
+ {
+ DrawStatSprite(2201, 34, 187, 16, nPalette, 256);
+ if (pXSprite->health >= 16 || (gGameClock&16) || pXSprite->health == 0)
+ {
+ DrawStatNumber("%3d", pXSprite->health>>4, 2190, 8, 183, 0, 0, 256);
+ }
+ if (pPlayer->atbd && pPlayer->atc7 != -1)
+ {
+ int num = pPlayer->at181[pPlayer->atc7];
+ if (pPlayer->atc7 == 6)
+ num /= 10;
+ DrawStatNumber("%3d", num, 2240, 42, 183, 0, 0, 256);
+ }
+ DrawStatSprite(2173, 284, 187, 16, nPalette, 512);
+ if (pPlayer->at33e[1])
+ {
+ TileHGauge(2207, 250, 175, pPlayer->at33e[1], 3200, 512);
+ DrawStatNumber("%3d", pPlayer->at33e[1]>>4, 2230, 255, 178, 0, 0, 512);
+ }
+ if (pPlayer->at33e[0])
+ {
+ TileHGauge(2209, 250, 183, pPlayer->at33e[0], 3200, 512);
+ DrawStatNumber("%3d", pPlayer->at33e[0]>>4, 2230, 255, 186, 0, 0, 512);
+ }
+ if (pPlayer->at33e[2])
+ {
+ TileHGauge(2208, 250, 191, pPlayer->at33e[2], 3200, 512);
+ DrawStatNumber("%3d", pPlayer->at33e[2]>>4, 2230, 255, 194, 0, 0, 512);
+ }
+ DrawPackItemInStatusBar(pPlayer, 286, 186, 302, 183, 512);
+
+ for (int i = 0; i < 6; i++)
+ {
+ int nTile = 2220+i;
+ int x, nStat = 0;
+ int y = 200-6;
+ if (i&1)
+ {
+ x = 320-(78+(i>>1)*10);
+ nStat |= 512;
+ }
+ else
+ {
+ x = 73+(i>>1)*10;
+ nStat |= 256;
+ }
+ if (pPlayer->at88[i+1])
+ DrawStatSprite(nTile, x, y, 0, 0, nStat);
+#if 0
+ else
+ DrawStatSprite(nTile, x, y, 40, 5, nStat);
+#endif
+ }
+ viewDrawStats(pPlayer, 2, 140);
+ viewDrawPowerUps(pPlayer);
+ }
+ else if (gViewSize > 2)
+ {
+ viewDrawPack(pPlayer, 160, 200-tilesiz[2200].y);
+ DrawStatMaskedSprite(2200, 160, 172, 16, nPalette);
+ DrawPackItemInStatusBar(pPlayer, 265, 186, 260, 172);
+ if (pXSprite->health >= 16 || (gGameClock&16) || pXSprite->health == 0)
+ {
+ DrawStatNumber("%3d", pXSprite->health>>4, 2190, 86, 183, 0, 0);
+ }
+ if (pPlayer->atbd && pPlayer->atc7 != -1)
+ {
+ int num = pPlayer->at181[pPlayer->atc7];
+ if (pPlayer->atc7 == 6)
+ num /= 10;
+ DrawStatNumber("%3d", num, 2240, 216, 183, 0, 0);
+ }
+ for (int i = 9; i >= 1; i--)
+ {
+ int x = 135+((i-1)/3)*23;
+ int y = 182+((i-1)%3)*6;
+ int num = pPlayer->at181[i];
+ if (i == 6)
+ num /= 10;
+ if (i == pPlayer->atc7)
+ {
+ DrawStatNumber("%3d", num, 2230, x, y, -128, 10);
+ }
+ else
+ {
+ DrawStatNumber("%3d", num, 2230, x, y, 32, 10);
+ }
+ }
+
+ if (pPlayer->atc7 == 10)
+ {
+ DrawStatNumber("%2d", pPlayer->at181[10], 2230, 291, 194, -128, 10);
+ }
+ else
+ {
+ DrawStatNumber("%2d", pPlayer->at181[10], 2230, 291, 194, 32, 10);
+ }
+
+ if (pPlayer->atc7 == 11)
+ {
+ DrawStatNumber("%2d", pPlayer->at181[11], 2230, 309, 194, -128, 10);
+ }
+ else
+ {
+ DrawStatNumber("%2d", pPlayer->at181[11], 2230, 309, 194, 32, 10);
+ }
+
+ if (pPlayer->at33e[1])
+ {
+ TileHGauge(2207, 44, 174, pPlayer->at33e[1], 3200);
+ DrawStatNumber("%3d", pPlayer->at33e[1]>>4, 2230, 50, 177, 0, 0);
+ }
+ if (pPlayer->at33e[0])
+ {
+ TileHGauge(2209, 44, 182, pPlayer->at33e[0], 3200);
+ DrawStatNumber("%3d", pPlayer->at33e[0]>>4, 2230, 50, 185, 0, 0);
+ }
+ if (pPlayer->at33e[2])
+ {
+ TileHGauge(2208, 44, 190, pPlayer->at33e[2], 3200);
+ DrawStatNumber("%3d", pPlayer->at33e[2]>>4, 2230, 50, 193, 0, 0);
+ }
+ sprintf(gTempStr, "v%s", GetVersionString());
+ viewDrawText(3, gTempStr, 20, 191, 32, 0, 1, 0);
+
+ for (int i = 0; i < 6; i++)
+ {
+ int nTile = 2220+i;
+ int x = 73+(i&1)*173;
+ int y = 171+(i>>1)*11;
+ if (pPlayer->at88[i+1])
+ DrawStatSprite(nTile, x, y);
+ else
+ DrawStatSprite(nTile, x, y, 40, 5);
+ }
+ DrawStatMaskedSprite(2202, 118, 185, pPlayer->at2e ? 16 : 40);
+ DrawStatMaskedSprite(2202, 201, 185, pPlayer->at2e ? 16 : 40);
+ if (pPlayer->at1ba)
+ {
+ TileHGauge(2260, 124, 175, pPlayer->at1ba, 65536);
+ }
+ viewDrawStats(pPlayer, 2, 140);
+ viewDrawPowerUps(pPlayer);
+ }
+
+ viewDrawMapTitle();
+
+ if (gGameOptions.nGameType < 1) return;
+
+ if (gGameOptions.nGameType == 3)
+ {
+ int x = 1, y = 1;
+ if (dword_21EFD0[0] == 0 || (gGameClock & 8))
+ {
+ viewDrawText(0, "BLUE", x, y, -128, 10, 0, 0, 256);
+ dword_21EFD0[0] = ClipLow(dword_21EFD0[0]-arg, 0);
+ sprintf(gTempStr, "%-3d", dword_21EFB0[0]);
+ viewDrawText(0, gTempStr, x, y+10, -128, 10, 0, 0, 256);
+ }
+ x = 319;
+ if (dword_21EFD0[1] == 0 || (gGameClock & 8))
+ {
+ viewDrawText(0, "RED", x, y, -128, 7, 2, 0, 512);
+ dword_21EFD0[1] = ClipLow(dword_21EFD0[1]-arg, 0);
+ sprintf(gTempStr, "%3d", dword_21EFB0[1]);
+ viewDrawText(0, gTempStr, x, y+10, -128, 7, 2, 0, 512);
+ }
+ return;
+ }
+ for (int nRows = (gNetPlayers-1) / 4; nRows >= 0; nRows--)
+ {
+ for (int nCol = 0; nCol < 4; nCol++)
+ {
+ DrawStatSprite(2229, 40+nCol*80, 4+nRows*9, 16);
+ }
+ }
+ for (int i = 0, p = connecthead; p >= 0; i++, p = connectpoint2[p])
+ {
+ int x = 80*(i&3);
+ int y = 9*(i/4);
+ int col = gPlayer[p].at2ea&3;
+ char *name = gProfile[p].name;
+ if (gProfile[p].skill == 2)
+ sprintf(gTempStr, "%s", name);
+ else
+ sprintf(gTempStr, "%s [%d]", name, gProfile[p].skill);
+ Bstrupr(gTempStr);
+ viewDrawText(4, gTempStr, x+4, y+1, -128, 11+col, 0, 0);
+ sprintf(gTempStr, "%2d", gPlayer[p].at2c6);
+ viewDrawText(4, gTempStr, x+76, y+1, -128, 11+col, 2, 0);
+ }
+}
+
+int *lensTable;
+
+int gZoom = 1024;
+
+int dword_172CE0[16][3];
+
+void viewInit(void)
+{
+ initprintf("Initializing status bar\n");
+ InitStatusBar();
+ FontSet(0, 4096, 0);
+ FontSet(1, 4192, 1);
+ FontSet(2, 4288, 1);
+ FontSet(3, 4384, 1);
+ FontSet(4, 4480, 0);
+
+ DICTNODE *hLens = gSysRes.Lookup("LENS", "DAT");
+ dassert(hLens != NULL);
+ dassert(gSysRes.Size(hLens) == kLensSize * kLensSize * sizeof(int));
+
+ lensTable = (int*)gSysRes.Lock(hLens);
+#if B_BIG_ENDIAN == 1
+ for (int i = 0; i < kLensSize*kLensSize; i++)
+ {
+ lensTable[i] = B_LITTLE32(lensTable[i]);
+ }
+#endif
+ char *data = tileAllocTile(4077, kLensSize, kLensSize, 0, 0);
+ memset(data, 255, kLensSize*kLensSize);
+ gGameMessageMgr.SetState(gMessageState);
+ gGameMessageMgr.SetCoordinates(1, 1);
+ char nFont;
+ if (gMessageFont == 0)
+ nFont = 3;
+ else
+ nFont = 0;
+
+ gGameMessageMgr.SetFont(nFont);
+ gGameMessageMgr.SetMaxMessages(gMessageCount);
+ gGameMessageMgr.SetMessageTime(gMessageTime);
+
+ for (int i = 0; i < 16; i++)
+ {
+ dword_172CE0[i][0] = mulscale16(wrand(), 2048);
+ dword_172CE0[i][1] = mulscale16(wrand(), 2048);
+ dword_172CE0[i][2] = mulscale16(wrand(), 2048);
+ }
+ gViewMap.sub_25C38(0, 0, gZoom, 0, gFollowMap);
+
+ g_frameDelay = calcFrameDelay(r_maxfps + r_maxfpsoffset);
+
+ bLoadScreenCrcMatch = tileCRC(kLoadScreen) == kLoadScreenCRC;
+}
+
+void viewResizeView(int size)
+{
+ int xdimcorrect = ClipHigh(scale(ydim, 4, 3), xdim);
+ gViewXCenter = xdim-xdim/2;
+ gViewYCenter = ydim-ydim/2;
+ xscale = divscale16(xdim, 320);
+ xscalecorrect = divscale16(xdimcorrect, 320);
+ yscale = divscale16(ydim, 200);
+ xstep = divscale16(320, xdim);
+ ystep = divscale16(200, ydim);
+ gViewSize = ClipRange(size, 0, 7);
+ if (gViewSize <= 2)
+ {
+ gViewX0 = 0;
+ gViewX1 = xdim-1;
+ gViewY0 = 0;
+ gViewY1 = ydim-1;
+ if (gGameOptions.nGameType > 0 && gGameOptions.nGameType < 3)
+ {
+ gViewY0 = (tilesiz[2229].y*ydim*((gNetPlayers+3)/4))/200;
+ }
+ gViewX0S = divscale16(gViewX0, xscalecorrect);
+ gViewY0S = divscale16(gViewY0, yscale);
+ gViewX1S = divscale16(gViewX1, xscalecorrect);
+ gViewY1S = divscale16(gViewY1, yscale);
+ }
+ else
+ {
+ gViewX0 = 0;
+ gViewY0 = 0;
+ gViewX1 = xdim-1;
+ gViewY1 = ydim-1-(25*ydim)/200;
+ if (gGameOptions.nGameType > 0 && gGameOptions.nGameType < 3)
+ {
+ gViewY0 = (tilesiz[2229].y*ydim*((gNetPlayers+3)/4))/200;
+ }
+
+ int height = gViewY1-gViewY0;
+ gViewX0 += mulscale16(xdim*(gViewSize-3),4096);
+ gViewX1 -= mulscale16(xdim*(gViewSize-3),4096);
+ gViewY0 += mulscale16(height*(gViewSize-3),4096);
+ gViewY1 -= mulscale16(height*(gViewSize-3),4096);
+ gViewX0S = divscale16(gViewX0, xscalecorrect);
+ gViewY0S = divscale16(gViewY0, yscale);
+ gViewX1S = divscale16(gViewX1, xscalecorrect);
+ gViewY1S = divscale16(gViewY1, yscale);
+ }
+ videoSetViewableArea(gViewX0, gViewY0, gViewX1, gViewY1);
+ gGameMessageMgr.SetCoordinates(gViewX0S + 1, gViewY0S + 1);
+ viewSetCrosshairColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b);
+ viewUpdatePages();
+}
+
+#define kBackTile 253
+
+void UpdateFrame(void)
+{
+ viewTileSprite(kBackTile, 0, 0, 0, 0, xdim, gViewY0-3);
+ viewTileSprite(kBackTile, 0, 0, 0, gViewY1+4, xdim, ydim);
+ viewTileSprite(kBackTile, 0, 0, 0, gViewY0-3, gViewX0-3, gViewY1+4);
+ viewTileSprite(kBackTile, 0, 0, gViewX1+4, gViewY0-3, xdim, gViewY1+4);
+
+ viewTileSprite(kBackTile, 20, 0, gViewX0-3, gViewY0-3, gViewX0, gViewY1+1);
+ viewTileSprite(kBackTile, 20, 0, gViewX0, gViewY0-3, gViewX1+4, gViewY0);
+ viewTileSprite(kBackTile, 10, 1, gViewX1+1, gViewY0, gViewX1+4, gViewY1+4);
+ viewTileSprite(kBackTile, 10, 1, gViewX0-3, gViewY1+1, gViewX1+1, gViewY1+4);
+}
+
+void viewDrawInterface(int arg)
+{
+ if (gViewMode == 3/* && gViewSize >= 3*/ && (pcBackground != 0 || videoGetRenderMode() >= REND_POLYMOST))
+ {
+ UpdateFrame();
+ pcBackground--;
+ }
+ UpdateStatusBar(arg);
+}
+
+uspritetype *viewInsertTSprite(int nSector, int nStatnum, uspritetype *pSprite)
+{
+ int nTSprite = spritesortcnt;
+ uspritetype *pTSprite = &tsprite[nTSprite];
+ memset(pTSprite, 0, sizeof(uspritetype));
+ pTSprite->cstat = 128;
+ pTSprite->xrepeat = 64;
+ pTSprite->yrepeat = 64;
+ pTSprite->owner = -1;
+ pTSprite->extra = -1;
+ pTSprite->type = -spritesortcnt;
+ pTSprite->statnum = nStatnum;
+ pTSprite->sectnum = nSector;
+ spritesortcnt++;
+ if (pSprite)
+ {
+ pTSprite->x = pSprite->x;
+ pTSprite->y = pSprite->y;
+ pTSprite->z = pSprite->z;
+ pTSprite->owner = pSprite->owner;
+ pTSprite->ang = pSprite->ang;
+ }
+ if (videoGetRenderMode() >= REND_POLYMOST)
+ {
+ int nAngle = getangle(pTSprite->x-gView->pSprite->x, pTSprite->y-gView->pSprite->y);
+ pTSprite->x += Cos(nAngle)>>25;
+ pTSprite->y += Sin(nAngle)>>25;
+ }
+ return &tsprite[nTSprite];
+}
+
+int effectDetail[] = {
+ 4, 4, 4, 4, 0, 0, 0, 0, 0, 1, 4, 4, 0, 0, 0, 1, 0, 0, 0
+};
+
+uspritetype *viewAddEffect(int nTSprite, VIEW_EFFECT nViewEffect)
+{
+ dassert(nViewEffect >= 0 && nViewEffect < kViewEffectMax);
+ uspritetype *pTSprite = &tsprite[nTSprite];
+ if (gDetail < effectDetail[nViewEffect] || nTSprite >= kMaxViewSprites) return NULL;
+ switch (nViewEffect)
+ {
+ case VIEW_EFFECT_18:
+ for (int i = 0; i < 16; i++)
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int ang = (gFrameClock*2048)/120;
+ int nRand1 = dword_172CE0[i][0];
+ int nRand2 = dword_172CE0[i][1];
+ int nRand3 = dword_172CE0[i][2];
+ ang += nRand3;
+ int x = mulscale30(512, Cos(ang));
+ int y = mulscale30(512, Sin(ang));
+ int z = 0;
+ RotateYZ(&x, &y, &z, nRand1);
+ RotateXZ(&x, &y, &z, nRand2);
+ pNSprite->x = pTSprite->x + x;
+ pNSprite->y = pTSprite->y + y;
+ pNSprite->z = pTSprite->z + (z<<4);
+ pNSprite->picnum = 1720;
+ pNSprite->shade = -128;
+ }
+ break;
+ case VIEW_EFFECT_16:
+ case VIEW_EFFECT_17:
+ {
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->shade = -128;
+ pNSprite->pal = 0;
+ pNSprite->z = top;
+ if (nViewEffect == VIEW_EFFECT_16)
+ pNSprite->xrepeat = pNSprite->yrepeat = 24;
+ else
+ pNSprite->xrepeat = pNSprite->yrepeat = 64;
+ pNSprite->picnum = 3558;
+ return pNSprite;
+ }
+ case VIEW_EFFECT_15:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->z = pTSprite->z;
+ pNSprite->cstat |= 2;
+ pNSprite->shade = -128;
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = pTSprite->yrepeat;
+ pNSprite->picnum = 2135;
+ break;
+ }
+ case VIEW_EFFECT_14:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->shade = -128;
+ pNSprite->pal = 0;
+ pNSprite->xrepeat = pNSprite->yrepeat = 64;
+ pNSprite->picnum = 2605;
+ return pNSprite;
+ }
+ case VIEW_EFFECT_13:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->shade = 26;
+ pNSprite->pal = 0;
+ pNSprite->cstat |= 2;
+ pNSprite->xrepeat = pNSprite->yrepeat = 64;
+ pNSprite->picnum = 2089;
+ break;
+ }
+ case VIEW_EFFECT_11:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ pNSprite->shade = 26;
+ pNSprite->pal = 0;
+ pNSprite->cstat |= 2;
+ pNSprite->xrepeat = pNSprite->yrepeat = 24;
+ pNSprite->picnum = 626;
+ pNSprite->z = top;
+ break;
+ }
+ case VIEW_EFFECT_10:
+ {
+ int nAng = pTSprite->ang;
+ if (pTSprite->cstat & 16)
+ {
+ nAng = (nAng+512)&2047;
+ }
+ else
+ {
+ nAng = (nAng+1024)&2047;
+ }
+ for (int i = 0; i < 5 && spritesortcnt < kMaxViewSprites; i++)
+ {
+ int nSector = pTSprite->sectnum;
+ uspritetype *pNSprite = viewInsertTSprite(nSector, 32767, NULL);
+ int nLen = 128+(i<<7);
+ int x = mulscale30(nLen, Cos(nAng));
+ pNSprite->x = pTSprite->x + x;
+ int y = mulscale30(nLen, Sin(nAng));
+ pNSprite->y = pTSprite->y + y;
+ pNSprite->z = pTSprite->z;
+ dassert(nSector >= 0 && nSector < kMaxSectors);
+ FindSector(pNSprite->x, pNSprite->y, pNSprite->z, &nSector);
+ pNSprite->sectnum = nSector;
+ pNSprite->owner = pTSprite->owner;
+ pNSprite->picnum = pTSprite->picnum;
+ pNSprite->cstat |= 2;
+ if (i < 2)
+ pNSprite->cstat |= 514;
+ pNSprite->shade = ClipLow(pTSprite->shade-16, -128);
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = pTSprite->yrepeat;
+ pNSprite->picnum = pTSprite->picnum;
+ }
+ break;
+ }
+ case VIEW_EFFECT_8:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->shade = -128;
+ pNSprite->z = pTSprite->z;
+ pNSprite->picnum = 908;
+ pNSprite->statnum = 0;
+ pNSprite->xrepeat = pNSprite->yrepeat = (tilesiz[pTSprite->picnum].x*pTSprite->xrepeat)/64;
+ break;
+ }
+ case VIEW_EFFECT_6:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ pNSprite->z = top;
+ if (IsDudeSprite((spritetype *)pTSprite))
+ pNSprite->picnum = 672;
+ else
+ pNSprite->picnum = 754;
+ pNSprite->cstat |= 2;
+ pNSprite->shade = 8;
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = pTSprite->yrepeat;
+ break;
+ }
+ case VIEW_EFFECT_7:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ pNSprite->z = bottom;
+ if (pTSprite->type >= kDudeBase && pTSprite->type < kDudeMax)
+ pNSprite->picnum = 672;
+ else
+ pNSprite->picnum = 754;
+ pNSprite->cstat |= 2;
+ pNSprite->shade = 8;
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = pTSprite->yrepeat;
+ break;
+ }
+ case VIEW_EFFECT_4:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ pNSprite->z = top;
+ pNSprite->picnum = 2101;
+ pNSprite->shade = -128;
+ pNSprite->xrepeat = pNSprite->yrepeat = (tilesiz[pTSprite->picnum].x*pTSprite->xrepeat)/32;
+ break;
+ }
+ case VIEW_EFFECT_5:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ pNSprite->z = bottom;
+ pNSprite->picnum = 2101;
+ pNSprite->shade = -128;
+ pNSprite->xrepeat = pNSprite->yrepeat = (tilesiz[pTSprite->picnum].x*pTSprite->xrepeat)/32;
+ break;
+ }
+ case VIEW_EFFECT_0:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->z = getflorzofslope(pTSprite->sectnum, pNSprite->x, pNSprite->y);
+ pNSprite->shade = 127;
+ pNSprite->cstat |= 2;
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = pTSprite->yrepeat>>2;
+ pNSprite->picnum = pTSprite->picnum;
+ pNSprite->pal = 5;
+ int height = tilesiz[pNSprite->picnum].y;
+ int center = height/2+picanm[pNSprite->picnum].yofs;
+ pNSprite->z -= (pNSprite->yrepeat<<2)*(height-center);
+ break;
+ }
+ case VIEW_EFFECT_1:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->shade = -128;
+ pNSprite->pal = 2;
+ pNSprite->cstat |= 2;
+ pNSprite->z = pTSprite->z;
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = pTSprite->yrepeat;
+ pNSprite->picnum = 2427;
+ break;
+ }
+ case VIEW_EFFECT_2:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ sectortype *pSector = §or[pTSprite->sectnum];
+ pNSprite->x = pTSprite->x;
+ pNSprite->y = pTSprite->y;
+ pNSprite->z = pSector->ceilingz;
+ pNSprite->picnum = 624;
+ pNSprite->shade = ((pTSprite->z-pSector->ceilingz)>>8)-64;
+ pNSprite->pal = 2;
+ pNSprite->xrepeat = pNSprite->yrepeat = 64;
+ pNSprite->cstat |= 106;
+ pNSprite->ang = pTSprite->ang;
+ pNSprite->owner = pTSprite->owner;
+ break;
+ }
+ case VIEW_EFFECT_3:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ sectortype *pSector = §or[pTSprite->sectnum];
+ pNSprite->x = pTSprite->x;
+ pNSprite->y = pTSprite->y;
+ pNSprite->z = pSector->floorz;
+ pNSprite->picnum = 624;
+ char nShade = (pSector->floorz-pTSprite->z)>>8;
+ pNSprite->shade = nShade-32;
+ pNSprite->pal = 2;
+ pNSprite->xrepeat = pNSprite->yrepeat = nShade;
+ pNSprite->cstat |= 98;
+ pNSprite->ang = pTSprite->ang;
+ pNSprite->owner = pTSprite->owner;
+ break;
+ }
+ case VIEW_EFFECT_9:
+ {
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ pNSprite->z = pTSprite->z;
+ if (gDetail > 1)
+ pNSprite->cstat |= 514;
+ pNSprite->shade = ClipLow(pTSprite->shade-32, -128);
+ pNSprite->xrepeat = pTSprite->xrepeat;
+ pNSprite->yrepeat = 64;
+ pNSprite->picnum = 775;
+ break;
+ }
+ case VIEW_EFFECT_12:
+ {
+ dassert(pTSprite->type >= kDudePlayer1 && pTSprite->type <= kDudePlayer8);
+ PLAYER *pPlayer = &gPlayer[pTSprite->type-kDudePlayer1];
+ if (gWeaponIcon[pPlayer->atbd].nTile < 0) break;
+ uspritetype *pNSprite = viewInsertTSprite(pTSprite->sectnum, 32767, pTSprite);
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ pNSprite->x = pTSprite->x;
+ pNSprite->y = pTSprite->y;
+ pNSprite->z = pTSprite->z-(32<<8);
+ pNSprite->picnum = gWeaponIcon[pPlayer->atbd].nTile;
+ pNSprite->shade = pTSprite->shade;
+ pNSprite->xrepeat = gWeaponIcon[pPlayer->atbd].xRepeat;
+ pNSprite->yrepeat = gWeaponIcon[pPlayer->atbd].yRepeat;
+ break;
+ }
+ }
+ return NULL;
+}
+
+LOCATION gPrevSpriteLoc[kMaxSprites];
+
+void viewProcessSprites(int cX, int cY, int cZ)
+{
+ dassert(spritesortcnt <= kMaxViewSprites);
+ int nViewSprites = spritesortcnt;
+ for (int nTSprite = nViewSprites-1; nTSprite >= 0; nTSprite--)
+ {
+ uspritetype *pTSprite = &tsprite[nTSprite];
+ //int nXSprite = pTSprite->extra;
+ int nXSprite = sprite[pTSprite->owner].extra;
+ XSPRITE *pTXSprite = NULL;
+ if (qsprite_filler[pTSprite->owner] > gDetail)
+ {
+ pTSprite->xrepeat = 0;
+ continue;
+ }
+ if (nXSprite > 0)
+ {
+ pTXSprite = &xsprite[nXSprite];
+ }
+ int nTile = pTSprite->picnum;
+ if (nTile < 0 || nTile >= kMaxTiles)
+ {
+ continue;
+ }
+
+ int nSprite = pTSprite->owner;
+ if (gViewInterpolate && TestBitString(gInterpolateSprite, nSprite) && !(pTSprite->hitag&512))
+ {
+ LOCATION *pPrevLoc = &gPrevSpriteLoc[nSprite];
+ pTSprite->x = interpolate(pPrevLoc->x, pTSprite->x, gInterpolate);
+ pTSprite->y = interpolate(pPrevLoc->y, pTSprite->y, gInterpolate);
+ pTSprite->z = interpolate(pPrevLoc->z, pTSprite->z, gInterpolate);
+ pTSprite->ang = pPrevLoc->ang+mulscale16(((pTSprite->ang-pPrevLoc->ang+1024)&2047)-1024, gInterpolate);
+ }
+ int nAnim = 0;
+ switch (picanm[nTile].extra&7)
+ {
+ case 0:
+ if (nXSprite > 0)
+ {
+ dassert(nXSprite < kMaxXSprites);
+ switch (pTSprite->type)
+ {
+ case 20:
+ case 21:
+ if (xsprite[nXSprite].state)
+ {
+ nAnim = 1;
+ }
+ break;
+ case 22:
+ nAnim = xsprite[nXSprite].data1;
+ break;
+ }
+ }
+ break;
+ case 1:
+ {
+#ifdef USE_OPENGL
+ if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pTSprite->picnum, pTSprite->pal) >= 0 && !(spriteext[nSprite].flags&SPREXT_NOTMD))
+ {
+ pTSprite->cstat &= ~4;
+ break;
+ }
+#endif
+ int dX = cX - pTSprite->x;
+ int dY = cY - pTSprite->y;
+ RotateVector(&dX, &dY, 128-pTSprite->ang);
+ nAnim = GetOctant(dX, dY);
+ if (nAnim <= 4)
+ {
+ pTSprite->cstat &= ~4;
+ }
+ else
+ {
+ nAnim = 8 - nAnim;
+ pTSprite->cstat |= 4;
+ }
+ break;
+ }
+ case 2:
+ {
+#ifdef USE_OPENGL
+ if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pTSprite->picnum, pTSprite->pal) >= 0 && !(spriteext[nSprite].flags&SPREXT_NOTMD))
+ {
+ pTSprite->cstat &= ~4;
+ break;
+ }
+#endif
+ int dX = cX - pTSprite->x;
+ int dY = cY - pTSprite->y;
+ RotateVector(&dX, &dY, 128-pTSprite->ang);
+ nAnim = GetOctant(dX, dY);
+ break;
+ }
+ case 3:
+ {
+ if (nXSprite > 0)
+ {
+ if (gSpriteHit[nXSprite].florhit == 0)
+ nAnim = 1;
+ }
+ else
+ {
+ int top, bottom;
+ GetSpriteExtents((spritetype *)pTSprite, &top, &bottom);
+ if (getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) > bottom)
+ nAnim = 1;
+ }
+ break;
+ }
+ case 6:
+ case 7:
+ {
+#ifdef USE_OPENGL
+ if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pTSprite->picnum, pTSprite->pal) >= 0 && !(spriteext[nSprite].flags&SPREXT_NOTMD))
+ break;
+#endif
+ // Can be overridden by def script
+ if (usevoxels && gDetail >= 4 && videoGetRenderMode() != REND_POLYMER && tiletovox[pTSprite->picnum] == -1 && voxelIndex[pTSprite->picnum] != -1)
+ {
+ if ((pTSprite->hitag&16) == 0)
+ {
+ pTSprite->cstat |= 48;
+ pTSprite->yoffset += picanm[pTSprite->picnum].yofs;
+ pTSprite->picnum = voxelIndex[pTSprite->picnum];
+ if (!voxoff[pTSprite->picnum])
+ qloadvoxel(pTSprite->picnum);
+ if ((picanm[nTile].extra&7) == 7)
+ {
+ pTSprite->ang = (gGameClock<<3)&2047;
+ }
+ }
+ }
+ break;
+ }
+ }
+ while (nAnim > 0)
+ {
+ pTSprite->picnum += picanm[pTSprite->picnum].num+1;
+ nAnim--;
+ }
+
+ if (usevoxels && videoGetRenderMode() != REND_POLYMER && tiletovox[pTSprite->picnum] != -1)
+ {
+ pTSprite->yoffset += picanm[pTSprite->picnum].yofs;
+ }
+
+ sectortype *pSector = §or[pTSprite->sectnum];
+ XSECTOR *pXSector;
+ int nShade = pTSprite->shade;
+ if (pSector->extra)
+ {
+ pXSector = &xsector[pSector->extra];
+ }
+ else
+ {
+ pXSector = NULL;
+ }
+ if ((pSector->ceilingstat&1) && (pSector->floorstat&32768) == 0)
+ {
+ nShade += tileShade[pSector->ceilingpicnum]+pSector->ceilingshade;
+ }
+ else
+ {
+ nShade += tileShade[pSector->floorpicnum]+pSector->floorshade;
+ }
+ nShade += tileShade[pTSprite->picnum];
+ pTSprite->shade = ClipRange(nShade, -128, 127);
+ if ((pTSprite->hitag&16) && sprite[pTSprite->owner].owner == 3)
+ {
+ dassert(pTXSprite != NULL);
+ pTSprite->xrepeat = 48;
+ pTSprite->yrepeat = 48;
+ pTSprite->shade = -128;
+ pTSprite->picnum = 2272 + 2*pTXSprite->respawnPending;
+ pTSprite->cstat &= ~514;
+ if (((IsItemSprite((spritetype *)pTSprite) || IsAmmoSprite((spritetype *)pTSprite)) && gGameOptions.nItemSettings == 2)
+ || (IsWeaponSprite((spritetype *)pTSprite) && gGameOptions.nWeaponSettings == 3))
+ {
+ pTSprite->xrepeat = pTSprite->yrepeat = 48;
+ }
+ else
+ {
+ pTSprite->xrepeat = pTSprite->yrepeat = 0;
+ }
+ }
+ if (spritesortcnt >= kMaxViewSprites) continue;
+ if (pTXSprite && pTXSprite->burnTime > 0)
+ {
+ pTSprite->shade = ClipRange(pTSprite->shade-16-QRandom(8), -128, 127);
+ }
+ if (pTSprite->hitag&256)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_6);
+ }
+ if (pTSprite->hitag&1024)
+ {
+ pTSprite->cstat |= 4;
+ }
+ if (pTSprite->hitag&2048)
+ {
+ pTSprite->cstat |= 8;
+ }
+ switch (pTSprite->statnum)
+ {
+ case 0:
+ {
+ switch (pTSprite->type)
+ {
+ case 32:
+ if (pTXSprite)
+ {
+ if (pTXSprite->state > 0)
+ {
+ pTSprite->shade = -128;
+ viewAddEffect(nTSprite, VIEW_EFFECT_11);
+ }
+ else
+ {
+ pTSprite->shade = -8;
+ }
+ }
+ else
+ {
+ pTSprite->shade = -128;
+ viewAddEffect(nTSprite, VIEW_EFFECT_11);
+ }
+ break;
+ case 30:
+ if (pTXSprite)
+ {
+ if (pTXSprite->state > 0)
+ {
+ pTSprite->picnum++;
+ viewAddEffect(nTSprite, VIEW_EFFECT_4);
+ }
+ else
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_6);
+ }
+ }
+ else
+ {
+ pTSprite->picnum++;
+ viewAddEffect(nTSprite, VIEW_EFFECT_4);
+ }
+ break;
+ default:
+ if (pXSector && pXSector->color)
+ {
+ pTSprite->pal = pSector->floorpal;
+ }
+ break;
+ }
+ break;
+ }
+ case 3:
+ {
+ switch (pTSprite->type)
+ {
+ case 145:
+ if (pTXSprite && pTXSprite->state > 0 && gGameOptions.nGameType == 3)
+ {
+ uspritetype *pNTSprite = viewAddEffect(nTSprite, VIEW_EFFECT_17);
+ if (pNTSprite)
+ pNTSprite->pal = 10;
+ }
+ break;
+ case 146:
+ if (pTXSprite && pTXSprite->state > 0 && gGameOptions.nGameType == 3)
+ {
+ uspritetype *pNTSprite = viewAddEffect(nTSprite, VIEW_EFFECT_17);
+ if (pNTSprite)
+ pNTSprite->pal = 7;
+ }
+ break;
+ case 147:
+ pTSprite->pal = 10;
+ pTSprite->cstat |= 1024;
+ break;
+ case 148:
+ pTSprite->pal = 7;
+ pTSprite->cstat |= 1024;
+ break;
+ default:
+ if (pTSprite->type >= 100 && pTSprite->type <= 106)
+ pTSprite->shade = -128;
+ if (pXSector && pXSector->color)
+ {
+ pTSprite->pal = pSector->floorpal;
+ }
+ break;
+ }
+ break;
+ }
+ case 5:
+ {
+ switch (pTSprite->type)
+ {
+ case 302:
+ pTSprite->yrepeat = 128;
+ pTSprite->cstat |= 32;
+ break;
+ case 306:
+ viewAddEffect(nTSprite, VIEW_EFFECT_15);
+ break;
+ case 300:
+ viewAddEffect(nTSprite, VIEW_EFFECT_10);
+ break;
+ case 301:
+ case 303:
+ if (pTSprite->statnum == 14)
+ {
+ dassert(pTXSprite != NULL);
+ if (pTXSprite->target == gView->at5b)
+ {
+ pTSprite->xrepeat = 0;
+ break;
+ }
+ }
+ viewAddEffect(nTSprite, VIEW_EFFECT_1);
+ if (pTSprite->type == 301)
+ {
+ sectortype *pSector = §or[pTSprite->sectnum];
+ int zDiff = (pTSprite->z-pSector->ceilingz)>>8;
+ if ((pSector->ceilingstat&1) == 0 && zDiff < 64)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_2);
+ }
+ zDiff = (pSector->floorz-pTSprite->z)>>8;
+ if ((pSector->floorstat&1) == 0 && zDiff < 64)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_3);
+ }
+ }
+ break;
+ }
+ break;
+ }
+ case 6:
+ {
+ if (pTSprite->type == 212 && pTXSprite->aiState == &hand13A3B4)
+ {
+ spritetype *pTTarget = &sprite[pTXSprite->target];
+ dassert(pTXSprite != NULL && pTTarget != NULL);
+ if (IsPlayerSprite(pTTarget))
+ {
+ pTSprite->xrepeat = 0;
+ break;
+ }
+ }
+ if (pXSector && pXSector->color)
+ {
+ pTSprite->pal = pSector->floorpal;
+ }
+ if (powerupCheck(gView, 25) > 0)
+ {
+ pTSprite->shade = -128;
+ }
+ if (IsPlayerSprite((spritetype *)pTSprite))
+ {
+ PLAYER *pPlayer = &gPlayer[pTSprite->type-kDudePlayer1];
+ if (powerupCheck(pPlayer, 13) && !powerupCheck(gView, 25))
+ {
+ pTSprite->cstat |= 2;
+ pTSprite->pal = 5;
+ }
+ else if (powerupCheck(pPlayer, 14))
+ {
+ pTSprite->shade = -128;
+ pTSprite->pal = 5;
+ }
+ else if (powerupCheck(pPlayer, 23))
+ {
+ pTSprite->pal = 11+(gView->at2ea&3);
+ }
+ if (powerupCheck(pPlayer, 24))
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_13);
+ }
+ if (gShowWeapon && gGameOptions.nGameType > 0 && gView)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_12);
+ }
+ if (pPlayer->at37b && (gView != pPlayer || gViewPos != VIEWPOS_0))
+ {
+ uspritetype *pNTSprite = viewAddEffect(nTSprite, VIEW_EFFECT_14);
+ if (pNTSprite)
+ {
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][pPlayer->at2f];
+ pNTSprite->x += mulscale28(pPosture->at30, Cos(pTSprite->ang));
+ pNTSprite->y += mulscale28(pPosture->at30, Sin(pTSprite->ang));
+ pNTSprite->z = pPlayer->pSprite->z-pPosture->at2c;
+ }
+ }
+ if (pPlayer->at90 > 0 && gGameOptions.nGameType == 3)
+ {
+ if (pPlayer->at90&1)
+ {
+ uspritetype *pNTSprite = viewAddEffect(nTSprite, VIEW_EFFECT_16);
+ if (pNTSprite)
+ {
+ pNTSprite->pal = 10;
+ pNTSprite->cstat |= 4;
+ }
+ }
+ if (pPlayer->at90&2)
+ {
+ uspritetype *pNTSprite = viewAddEffect(nTSprite, VIEW_EFFECT_16);
+ if (pNTSprite)
+ {
+ pNTSprite->pal = 7;
+ pNTSprite->cstat |= 4;
+ }
+ }
+ }
+ }
+ if (pTSprite->owner != gView->pSprite->index || gViewPos != VIEWPOS_0)
+ {
+ if (getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) >= cZ)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_0);
+ }
+ }
+ break;
+ }
+ case 11:
+ {
+ if (pTSprite->type == 454)
+ {
+ if (pTXSprite->state)
+ {
+ if (pTXSprite->data1)
+ {
+ pTSprite->picnum = 772;
+ if (pTXSprite->data2)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_9);
+ }
+ }
+ }
+ else
+ {
+ if (pTXSprite->data1)
+ {
+ pTSprite->picnum = 773;
+ }
+ else
+ {
+ pTSprite->picnum = 656;
+ }
+ }
+ }
+ break;
+ }
+ case 4:
+ {
+ if (pXSector && pXSector->color)
+ {
+ pTSprite->pal = pSector->floorpal;
+ }
+ if (pTSprite->hitag&1)
+ {
+ if (getflorzofslope(pTSprite->sectnum, pTSprite->x, pTSprite->y) >= cZ)
+ {
+ if (pTSprite->type < 400 || pTSprite->type >= 433 || !gSpriteHit[nXSprite].florhit)
+ {
+ viewAddEffect(nTSprite, VIEW_EFFECT_0);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ for (int nTSprite = spritesortcnt-1; nTSprite >= nViewSprites; nTSprite--)
+ {
+ uspritetype *pTSprite = &tsprite[nTSprite];
+ int nAnim = 0;
+ switch (picanm[pTSprite->picnum].extra&7)
+ {
+ case 1:
+ {
+ int dX = cX - pTSprite->x;
+ int dY = cY - pTSprite->y;
+ RotateVector(&dX, &dY, 128-pTSprite->ang);
+ nAnim = GetOctant(dX, dY);
+ if (nAnim <= 4)
+ {
+ pTSprite->cstat &= ~4;
+ }
+ else
+ {
+ nAnim = 8 - nAnim;
+ pTSprite->cstat |= 4;
+ }
+ break;
+ }
+ case 2:
+ {
+ int dX = cX - pTSprite->x;
+ int dY = cY - pTSprite->y;
+ RotateVector(&dX, &dY, 128-pTSprite->ang);
+ nAnim = GetOctant(dX, dY);
+ break;
+ }
+ }
+ while (nAnim > 0)
+ {
+ pTSprite->picnum += picanm[pTSprite->picnum].num+1;
+ nAnim--;
+ }
+ }
+}
+
+int othercameradist = 1280;
+int cameradist = -1;
+int othercameraclock;
+int cameraclock;
+
+void CalcOtherPosition(spritetype *pSprite, int *pX, int *pY, int *pZ, int *vsectnum, int nAng, fix16_t zm)
+{
+ int vX = mulscale30(-Cos(nAng), 1280);
+ int vY = mulscale30(-Sin(nAng), 1280);
+ int vZ = fix16_to_int(mulscale(zm, 1280, 3))-(16<<8);
+ int bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~256;
+ dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
+ FindSector(*pX, *pY, *pZ, vsectnum);
+ short nHSector;
+ int hX, hY;
+ vec3_t pos = {*pX, *pY, *pZ};
+ hitdata_t hitdata;
+ hitscan(&pos, *vsectnum, vX, vY, vZ, &hitdata, CLIPMASK1);
+ nHSector = hitdata.sect;
+ hX = hitdata.pos.x;
+ hY = hitdata.pos.y;
+ int dX = hX-*pX;
+ int dY = hY-*pY;
+ if (klabs(vX)+klabs(vY) > klabs(dX)+klabs(dY))
+ {
+ *vsectnum = nHSector;
+ dX -= ksgn(vX)<<6;
+ dY -= ksgn(vY)<<6;
+ int nDist;
+ if (klabs(vX) > klabs(vY))
+ {
+ nDist = ClipHigh(divscale16(dX,vX), othercameradist);
+ }
+ else
+ {
+ nDist = ClipHigh(divscale16(dY,vY), othercameradist);
+ }
+ othercameradist = nDist;
+ }
+ *pX += mulscale16(vX, othercameradist);
+ *pY += mulscale16(vY, othercameradist);
+ *pZ += mulscale16(vZ, othercameradist);
+ othercameradist = ClipHigh(othercameradist+((gGameClock-othercameraclock)<<10), 65536);
+ othercameraclock = gGameClock;
+ dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
+ FindSector(*pX, *pY, *pZ, vsectnum);
+ pSprite->cstat = bakCstat;
+}
+
+void CalcPosition(spritetype *pSprite, int *pX, int *pY, int *pZ, int *vsectnum, int nAng, fix16_t zm)
+{
+ int vX = mulscale30(-Cos(nAng), 1280);
+ int vY = mulscale30(-Sin(nAng), 1280);
+ int vZ = fix16_to_int(mulscale(zm, 1280, 3))-(16<<8);
+ int bakCstat = pSprite->cstat;
+ pSprite->cstat &= ~256;
+ dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
+ FindSector(*pX, *pY, *pZ, vsectnum);
+ short nHSector;
+ int hX, hY;
+ hitscangoal.x = hitscangoal.y = 0x1fffffff;
+ vec3_t pos = { *pX, *pY, *pZ };
+ hitdata_t hitdata;
+ hitscan(&pos, *vsectnum, vX, vY, vZ, &hitdata, CLIPMASK1);
+ nHSector = hitdata.sect;
+ hX = hitdata.pos.x;
+ hY = hitdata.pos.y;
+ int dX = hX-*pX;
+ int dY = hY-*pY;
+ if (klabs(vX)+klabs(vY) > klabs(dX)+klabs(dY))
+ {
+ *vsectnum = nHSector;
+ dX -= ksgn(vX)<<6;
+ dY -= ksgn(vY)<<6;
+ int nDist;
+ if (klabs(vX) > klabs(vY))
+ {
+ nDist = ClipHigh(divscale16(dX,vX), cameradist);
+ }
+ else
+ {
+ nDist = ClipHigh(divscale16(dY,vY), cameradist);
+ }
+ cameradist = nDist;
+ }
+ *pX += mulscale16(vX, cameradist);
+ *pY += mulscale16(vY, cameradist);
+ *pZ += mulscale16(vZ, cameradist);
+ cameradist = ClipHigh(cameradist+((gGameClock-cameraclock)<<10), 65536);
+ cameraclock = gGameClock;
+ dassert(*vsectnum >= 0 && *vsectnum < kMaxSectors);
+ FindSector(*pX, *pY, *pZ, vsectnum);
+ pSprite->cstat = bakCstat;
+}
+
+struct {
+ short nTile;
+ unsigned char nStat;
+ unsigned char nPal;
+ int nScale;
+ short nX, nY;
+} burnTable[9] = {
+ { 2101, 2, 0, 118784, 10, 220 },
+ { 2101, 2, 0, 110592, 40, 220 },
+ { 2101, 2, 0, 81920, 85, 220 },
+ { 2101, 2, 0, 69632, 120, 220 },
+ { 2101, 2, 0, 61440, 160, 220 },
+ { 2101, 2, 0, 73728, 200, 220 },
+ { 2101, 2, 0, 77824, 235, 220 },
+ { 2101, 2, 0, 110592, 275, 220 },
+ { 2101, 2, 0, 122880, 310, 220 }
+};
+
+void viewBurnTime(int gScale)
+{
+ if (!gScale) return;
+
+ for (int i = 0; i < 9; i++)
+ {
+ int nTile = burnTable[i].nTile+qanimateoffs(burnTable[i].nTile,32768+i);
+ int nScale = burnTable[i].nScale;
+ if (gScale < 600)
+ {
+ nScale = scale(nScale, gScale, 600);
+ }
+ rotatesprite(burnTable[i].nX<<16, burnTable[i].nY<<16, nScale, 0, nTile,
+ 0, burnTable[i].nPal, burnTable[i].nStat, windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y);
+ }
+}
+
+void viewSetMessage(const char *pMessage)
+{
+ OSD_Printf("%s\n", pMessage);
+ gGameMessageMgr.Add(pMessage, 15);
+}
+
+void viewDisplayMessage(void)
+{
+ gGameMessageMgr.Display();
+}
+
+char errMsg[256];
+
+void viewSetErrorMessage(const char *pMessage)
+{
+ if (!pMessage)
+ {
+ strcpy(errMsg, "");
+ }
+ else
+ {
+ strcpy(errMsg, pMessage);
+ }
+}
+
+void DoLensEffect(void)
+{
+ char *d = (char*)waloff[4077];
+ dassert(d != NULL);
+ char *s = (char*)waloff[4079];
+ dassert(s != NULL);
+ for (int i = 0; i < kLensSize*kLensSize; i++, d++)
+ if (lensTable[i] >= 0)
+ *d = s[lensTable[i]];
+ tileInvalidate(4077, -1, -1);
+}
+
+void UpdateDacs(int nPalette, bool bNoTint)
+{
+ static RGB newDAC[256];
+ static int oldPalette;
+ if (oldPalette != nPalette)
+ {
+ scrSetPalette(nPalette);
+ oldPalette = nPalette;
+ }
+
+#ifdef USE_OPENGL
+ if (videoGetRenderMode() >= REND_POLYMOST)
+ {
+ gLastPal = 0;
+ polytint_t *tint = &hictinting[MAXPALOOKUPS-1];
+ int nRed = 0;
+ int nGreen = 0;
+ int nBlue = 0;
+ tint->f = 0;
+ switch (nPalette)
+ {
+ case 0:
+ default:
+ tint->r = 255;
+ tint->g = 255;
+ tint->b = 255;
+ break;
+ case 1:
+ tint->r = 132;
+ tint->g = 164;
+ tint->b = 255;
+ break;
+ case 2:
+ tint->r = 255;
+ tint->g = 126;
+ tint->b = 105;
+ break;
+ case 3:
+ tint->r = 162;
+ tint->g = 186;
+ tint->b = 15;
+ break;
+ case 4:
+ tint->r = 255;
+ tint->g = 255;
+ tint->b = 255;
+ break;
+ }
+ if (!bNoTint)
+ {
+ nRed += gView->at377;
+ nGreen += gView->at377;
+ nBlue -= gView->at377;
+
+ nRed += ClipHigh(gView->at366, 85)*2;
+ nGreen -= ClipHigh(gView->at366, 85)*3;
+ nBlue -= ClipHigh(gView->at366, 85)*3;
+
+ nRed -= gView->at36a;
+ nGreen -= gView->at36a;
+ nBlue -= gView->at36a;
+
+ nRed -= gView->at36e>>6;
+ nGreen -= gView->at36e>>5;
+ nBlue -= gView->at36e>>6;
+ }
+ nRed = ClipRange(nRed, -255, 255);
+ nGreen = ClipRange(nGreen, -255, 255);
+ nBlue = ClipRange(nBlue, -255, 255);
+
+ videoSetPalette(0, nPalette, 2);
+ videoTintBlood(nRed, nGreen, nBlue);
+ }
+ else
+#endif
+ {
+ gLastPal = nPalette;
+ if (bNoTint)
+ {
+ memcpy(newDAC, baseDAC, sizeof(newDAC));
+ }
+ else
+ {
+ for (int i = 0; i < 256; i++)
+ {
+ int nRed = baseDAC[i].red;
+ int nGreen = baseDAC[i].green;
+ int nBlue = baseDAC[i].blue;
+ nRed += gView->at377;
+ nGreen += gView->at377;
+ nBlue -= gView->at377;
+
+ nRed += ClipHigh(gView->at366, 85)*2;
+ nGreen -= ClipHigh(gView->at366, 85)*3;
+ nBlue -= ClipHigh(gView->at366, 85)*3;
+
+ nRed -= gView->at36a;
+ nGreen -= gView->at36a;
+ nBlue -= gView->at36a;
+
+ nRed -= gView->at36e>>6;
+ nGreen -= gView->at36e>>5;
+ nBlue -= gView->at36e>>6;
+
+ newDAC[i].red = ClipRange(nRed, 0, 255);
+ newDAC[i].green = ClipRange(nGreen, 0, 255);
+ newDAC[i].blue = ClipRange(nBlue, 0, 255);
+ }
+ }
+ if (memcmp(newDAC, curDAC, 768) != 0)
+ {
+ memcpy(curDAC, newDAC, 768);
+ gSetDacRange(0, 256, curDAC);
+ }
+ }
+}
+
+bool gPrediction = true;
+
+char otherMirrorGotpic[2];
+char bakMirrorGotpic[2];
+// int gVisibility;
+
+int deliriumTilt, deliriumTurn, deliriumPitch;
+int gScreenTiltO, deliriumTurnO, deliriumPitchO;
+
+int gShowFrameRate = 1;
+
+void viewUpdateDelirium(void)
+{
+ gScreenTiltO = gScreenTilt;
+ deliriumTurnO = deliriumTurn;
+ deliriumPitchO = deliriumPitch;
+ int powerCount;
+ if ((powerCount = powerupCheck(gView,28)) != 0)
+ {
+ int tilt1 = 170, tilt2 = 170, pitch = 20;
+ int timer = gFrameClock*4;
+ if (powerCount < 512)
+ {
+ int powerScale = (powerCount<<16) / 512;
+ tilt1 = mulscale16(tilt1, powerScale);
+ tilt2 = mulscale16(tilt2, powerScale);
+ pitch = mulscale16(pitch, powerScale);
+ }
+ int sin2 = costable[(2*timer-512)&2047] / 2;
+ int sin3 = costable[(3*timer-512)&2047] / 2;
+ gScreenTilt = mulscale30(sin2+sin3,tilt1);
+ int sin4 = costable[(4*timer-512)&2047] / 2;
+ deliriumTurn = mulscale30(sin3+sin4,tilt2);
+ int sin5 = costable[(5*timer-512)&2047] / 2;
+ deliriumPitch = mulscale30(sin4+sin5,pitch);
+ return;
+ }
+ gScreenTilt = ((gScreenTilt+1024)&2047)-1024;
+ if (gScreenTilt > 0)
+ {
+ gScreenTilt -= 8;
+ if (gScreenTilt < 0)
+ gScreenTilt = 0;
+ }
+ else if (gScreenTilt < 0)
+ {
+ gScreenTilt += 8;
+ if (gScreenTilt >= 0)
+ gScreenTilt = 0;
+ }
+}
+
+int shakeHoriz, shakeAngle, shakeX, shakeY, shakeZ, shakeBobX, shakeBobY;
+
+void viewUpdateShake(void)
+{
+ shakeHoriz = 0;
+ shakeAngle = 0;
+ shakeX = 0;
+ shakeY = 0;
+ shakeZ = 0;
+ shakeBobX = 0;
+ shakeBobY = 0;
+ if (gView->at35a)
+ {
+ int nValue = ClipHigh(gView->at35a * 8, 2000);
+ shakeHoriz += QRandom2(nValue >> 8);
+ shakeAngle += QRandom2(nValue >> 8);
+ shakeX += QRandom2(nValue >> 4);
+ shakeY += QRandom2(nValue >> 4);
+ shakeZ += QRandom2(nValue);
+ shakeBobX += QRandom2(nValue);
+ shakeBobY += QRandom2(nValue);
+ }
+ if (gView->at37f)
+ {
+ int nValue = ClipHigh(gView->at37f * 8, 2000);
+ shakeHoriz += QRandom2(nValue >> 8);
+ shakeAngle += QRandom2(nValue >> 8);
+ shakeX += QRandom2(nValue >> 4);
+ shakeY += QRandom2(nValue >> 4);
+ shakeZ += QRandom2(nValue);
+ shakeBobX += QRandom2(nValue);
+ shakeBobY += QRandom2(nValue);
+ }
+}
+
+int32_t r_maxfps = 60;
+int32_t r_maxfpsoffset = 0;
+double g_frameDelay = 0.0;
+
+int viewFPSLimit(void)
+{
+ static auto nextPageTicks = (double)timerGetTicksU64();
+ static unsigned frameWaiting = 0;
+
+ if (frameWaiting)
+ {
+ frameWaiting--;
+ videoNextPage();
+ }
+
+ auto const frameTicks = (double)timerGetTicksU64();
+
+ if (!r_maxfps || frameTicks >= nextPageTicks)
+ {
+ if (frameTicks >= nextPageTicks + g_frameDelay)
+ nextPageTicks = frameTicks;
+
+ nextPageTicks += g_frameDelay;
+ frameWaiting++;
+ }
+
+ return frameWaiting;
+}
+
+float r_ambientlight = 1.0, r_ambientlightrecip = 1.0;
+
+int gLastPal = 0;
+
+int32_t g_frameRate;
+
+void viewDrawScreen(void)
+{
+ int nPalette = 0;
+ static int lastUpdate;
+ int defaultHoriz = gCenterHoriz ? 100 : 90;
+
+#ifdef USE_OPENGL
+ polymostcenterhoriz = defaultHoriz;
+#endif
+
+ timerUpdate();
+ int delta = ClipLow(gGameClock - lastUpdate, 0);
+ lastUpdate = gGameClock;
+ if (!gPaused && (!CGameMenuMgr::m_bActive || gGameOptions.nGameType != 0))
+ {
+ gInterpolate = divscale16(gGameClock-gNetFifoClock+4, 4);
+ }
+ if (gInterpolate < 0 || gInterpolate > 65536)
+ {
+ gInterpolate = ClipRange(gInterpolate, 0, 65536);
+ }
+ if (gViewInterpolate)
+ {
+ CalcInterpolations();
+ }
+
+ if (gViewMode == 3 || gViewMode == 4 || gOverlayMap)
+ {
+ DoSectorLighting();
+ }
+ if (gViewMode == 3 || gOverlayMap)
+ {
+ int yxAspect = yxaspect;
+ int viewingRange = viewingrange;
+ if (r_usenewaspect)
+ {
+ newaspect_enable = 1;
+ videoSetCorrectedAspect();
+ }
+ renderSetAspect(Blrintf(float(viewingrange) * tanf(gFov * (PI/360.f))), yxaspect);
+ int cX = gView->pSprite->x;
+ int cY = gView->pSprite->y;
+ int cZ = gView->at67;
+ int zDelta = gView->at6f-gView->at67-(12<<8);
+ fix16_t cA = gView->q16ang;
+ fix16_t q16horiz = gView->q16horiz;
+ fix16_t q16slopehoriz = gView->q16slopehoriz;
+ int v74 = gView->at43;
+ int v8c = gView->at3f;
+ int v4c = gView->at53;
+ int v48 = gView->at4f;
+ int nSectnum = gView->pSprite->sectnum;
+ if (gViewInterpolate)
+ {
+ if (numplayers > 1 && gView == gMe && gPrediction && gMe->pXSprite->health > 0)
+ {
+ nSectnum = predict.at68;
+ cX = interpolate(predictOld.at50, predict.at50, gInterpolate);
+ cY = interpolate(predictOld.at54, predict.at54, gInterpolate);
+ cZ = interpolate(predictOld.at38, predict.at38, gInterpolate);
+ zDelta = interpolate(predictOld.at34, predict.at34, gInterpolate);
+ cA = interpolateangfix16(predictOld.at30, predict.at30, gInterpolate);
+ q16horiz = interpolate(predictOld.at24, predict.at24, gInterpolate);
+ q16slopehoriz = interpolate(predictOld.at28, predict.at28, gInterpolate);
+ v74 = interpolate(predictOld.atc, predict.atc, gInterpolate);
+ v8c = interpolate(predictOld.at8, predict.at8, gInterpolate);
+ v4c = interpolate(predictOld.at1c, predict.at1c, gInterpolate);
+ v48 = interpolate(predictOld.at18, predict.at18, gInterpolate);
+ }
+ else
+ {
+ VIEW *pView = &gPrevView[gViewIndex];
+ cX = interpolate(pView->at50, cX, gInterpolate);
+ cY = interpolate(pView->at54, cY, gInterpolate);
+ cZ = interpolate(pView->at38, cZ, gInterpolate);
+ zDelta = interpolate(pView->at34, zDelta, gInterpolate);
+ cA = interpolateangfix16(pView->at30, cA, gInterpolate);
+ q16horiz = interpolate(pView->at24, q16horiz, gInterpolate);
+ q16slopehoriz = interpolate(pView->at28, q16slopehoriz, gInterpolate);
+ v74 = interpolate(pView->atc, v74, gInterpolate);
+ v8c = interpolate(pView->at8, v8c, gInterpolate);
+ v4c = interpolate(pView->at1c, v4c, gInterpolate);
+ v48 = interpolate(pView->at18, v48, gInterpolate);
+ }
+ }
+ viewUpdateShake();
+ q16horiz += fix16_from_int(shakeHoriz);
+ cA += fix16_from_int(shakeAngle);
+ cX += shakeX;
+ cY += shakeY;
+ cZ += shakeZ;
+ v4c += shakeBobX;
+ v48 += shakeBobY;
+ q16horiz += fix16_from_int(mulscale30(0x40000000-Cos(gView->at35e<<2), 30));
+ if (gViewPos == 0)
+ {
+ if (gViewHBobbing)
+ {
+ cX -= mulscale30(v74, Sin(fix16_to_int(cA)))>>4;
+ cY += mulscale30(v74, Cos(fix16_to_int(cA)))>>4;
+ }
+ if (gViewVBobbing)
+ {
+ cZ += v8c;
+ }
+ if (gSlopeTilting)
+ {
+ q16horiz += q16slopehoriz;
+ }
+ cZ += fix16_to_int(q16horiz*10);
+ cameradist = -1;
+ cameraclock = gGameClock;
+ }
+ else
+ {
+ CalcPosition(gView->pSprite, (int*)&cX, (int*)&cY, (int*)&cZ, &nSectnum, fix16_to_int(cA), q16horiz);
+ }
+ CheckLink((int*)&cX, (int*)&cY, (int*)&cZ, &nSectnum);
+ int v78 = interpolateang(gScreenTiltO, gScreenTilt, gInterpolate);
+ char v14 = 0;
+ char v10 = 0;
+ bool bDelirium = powerupCheck(gView, 28) > 0;
+ static bool bDeliriumOld = false;
+ int tiltcs, tiltdim;
+ char v4 = powerupCheck(gView, 21) > 0;
+#ifdef USE_OPENGL
+ renderSetRollAngle(0);
+#endif
+ if (v78 || bDelirium)
+ {
+ if (videoGetRenderMode() == REND_CLASSIC)
+ {
+ int vr = viewingrange;
+ walock[TILTBUFFER] = 255;
+ if (!waloff[TILTBUFFER])
+ {
+ tileAllocTile(TILTBUFFER, 640, 640, 0, 0);
+ }
+ if (xdim >= 640 && ydim >= 640)
+ {
+ tiltcs = 1;
+ tiltdim = 640;
+ }
+ else
+ {
+ tiltcs = 0;
+ tiltdim = 320;
+ }
+ renderSetTarget(TILTBUFFER, tiltdim, tiltdim);
+ int nAng = v78&511;
+ if (nAng > 256)
+ {
+ nAng = 512-nAng;
+ }
+ renderSetAspect(mulscale16(vr, dmulscale32(Cos(nAng), 262144, Sin(nAng), 163840)), yxaspect);
+ }
+#ifdef USE_OPENGL
+ else
+ renderSetRollAngle(v78);
+#endif
+ }
+ else if (v4 && gNetPlayers > 1)
+ {
+ int tmp = (gGameClock/240)%(gNetPlayers-1);
+ int i = connecthead;
+ while (1)
+ {
+ if (i == gViewIndex)
+ i = connectpoint2[i];
+ if (tmp == 0)
+ break;
+ i = connectpoint2[i];
+ tmp--;
+ }
+ PLAYER *pOther = &gPlayer[i];
+ //othercameraclock = gGameClock;
+ if (!waloff[4079])
+ {
+ tileAllocTile(4079, 128, 128, 0, 0);
+ }
+ renderSetTarget(4079, 128, 128);
+ renderSetAspect(65536, 78643);
+ int vd8 = pOther->pSprite->x;
+ int vd4 = pOther->pSprite->y;
+ int vd0 = pOther->at67;
+ int vcc = pOther->pSprite->sectnum;
+ int v50 = pOther->pSprite->ang;
+ int v54 = 0;
+ if (pOther->at35a)
+ {
+ int nValue = ClipHigh(pOther->at35a*8, 2000);
+ v54 += QRandom2(nValue>>8);
+ v50 += QRandom2(nValue>>8);
+ vd8 += QRandom2(nValue>>4);
+ vd4 += QRandom2(nValue>>4);
+ vd0 += QRandom2(nValue);
+ }
+ if (pOther->at37f)
+ {
+ int nValue = ClipHigh(pOther->at37f*8, 2000);
+ v54 += QRandom2(nValue >> 8);
+ v50 += QRandom2(nValue >> 8);
+ vd8 += QRandom2(nValue >> 4);
+ vd4 += QRandom2(nValue >> 4);
+ vd0 += QRandom2(nValue);
+ }
+ CalcOtherPosition(pOther->pSprite, &vd8, &vd4, &vd0, &vcc, v50, 0);
+ CheckLink(&vd8, &vd4, &vd0, &vcc);
+ if (IsUnderwaterSector(vcc))
+ {
+ v14 = 10;
+ }
+ memcpy(bakMirrorGotpic, gotpic+510, 2);
+ memcpy(gotpic+510, otherMirrorGotpic, 2);
+ g_visibility = (int32_t)(ClipLow(gVisibility-32*pOther->at362, 0) * (numplayers > 1 ? 1.f : r_ambientlightrecip));
+ int vc4, vc8;
+ getzsofslope(vcc, vd8, vd4, &vc8, &vc4);
+ if (vd0 >= vc4)
+ {
+ vd0 = vc4-(8<<4);
+ }
+ if (vd0 <= vc8)
+ {
+ vd0 = vc8+(8<<4);
+ }
+ v54 = ClipRange(v54, -200, 200);
+RORHACKOTHER:
+ int ror_status[16];
+ for (int i = 0; i < 16; i++)
+ ror_status[i] = TestBitString(gotpic, 4080 + i);
+ DrawMirrors(vd8, vd4, vd0, fix16_from_int(v50), fix16_from_int(v54 + defaultHoriz));
+ drawrooms(vd8, vd4, vd0, v50, v54 + defaultHoriz, vcc);
+ bool do_ror_hack = false;
+ for (int i = 0; i < 16; i++)
+ if (!ror_status[i] && TestBitString(gotpic, 4080 + i))
+ do_ror_hack = true;
+ if (do_ror_hack)
+ goto RORHACKOTHER;
+ memcpy(otherMirrorGotpic, gotpic+510, 2);
+ memcpy(gotpic+510, bakMirrorGotpic, 2);
+ viewProcessSprites(vd8, vd4, vd0);
+ renderDrawMasks();
+ renderRestoreTarget();
+ }
+ else
+ {
+ othercameraclock = gGameClock;
+ }
+
+ if (!bDelirium)
+ {
+ deliriumTilt = 0;
+ deliriumTurn = 0;
+ deliriumPitch = 0;
+ }
+ int nSprite = headspritestat[2];
+ int unk = 0;
+ while (nSprite >= 0)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (TestBitString(gotsector, pSprite->sectnum))
+ {
+ unk += pXSprite->data3*32;
+ }
+ nSprite = nextspritestat[nSprite];
+ }
+ nSprite = headspritestat[5];
+ while (nSprite >= 0)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ switch (pSprite->type)
+ {
+ case 301:
+ case 302:
+ case 303:
+ case 306:
+ if (TestBitString(gotsector, pSprite->sectnum))
+ {
+ unk += 256;
+ }
+ break;
+ }
+ nSprite = nextspritestat[nSprite];
+ }
+ g_visibility = (int32_t)(ClipLow(gVisibility - 32 * gView->at362 - unk, 0) * (numplayers > 1 ? 1.f : r_ambientlightrecip));
+ cA = (cA + interpolateangfix16(fix16_from_int(deliriumTurnO), fix16_from_int(deliriumTurn), gInterpolate)) & 0x7ffffff;
+ int vfc, vf8;
+ getzsofslope(nSectnum, cX, cY, &vfc, &vf8);
+ if (cZ >= vf8)
+ {
+ cZ = vf8-(8<<8);
+ }
+ if (cZ <= vfc)
+ {
+ cZ = vfc+(8<<8);
+ }
+ q16horiz = ClipRange(q16horiz, F16(-200), F16(200));
+RORHACK:
+ int ror_status[16];
+ for (int i = 0; i < 16; i++)
+ ror_status[i] = TestBitString(gotpic, 4080+i);
+ fix16_t deliriumPitchI = interpolate(fix16_from_int(deliriumPitchO), fix16_from_int(deliriumPitch), gInterpolate);
+ DrawMirrors(cX, cY, cZ, cA, q16horiz + fix16_from_int(defaultHoriz) + deliriumPitchI);
+ int bakCstat = gView->pSprite->cstat;
+ if (gViewPos == 0)
+ {
+ gView->pSprite->cstat |= 32768;
+ }
+ else
+ {
+ gView->pSprite->cstat |= 514;
+ }
+ renderDrawRoomsQ16(cX, cY, cZ, cA, q16horiz + fix16_from_int(defaultHoriz) + deliriumPitchI, nSectnum);
+ bool do_ror_hack = false;
+ for (int i = 0; i < 16; i++)
+ if (!ror_status[i] && TestBitString(gotpic, 4080+i))
+ do_ror_hack = true;
+ if (do_ror_hack)
+ {
+ gView->pSprite->cstat = bakCstat;
+ goto RORHACK;
+ }
+
+ viewProcessSprites(cX, cY, cZ);
+ sub_5571C(1);
+ renderDrawMasks();
+ sub_5571C(0);
+ sub_557C4(cX, cY, gInterpolate);
+ renderDrawMasks();
+ gView->pSprite->cstat = bakCstat;
+
+ if (v78 || bDelirium)
+ {
+ if (videoGetRenderMode() == REND_CLASSIC)
+ {
+ dassert(waloff[ TILTBUFFER ] != 0);
+ renderRestoreTarget();
+ int vrc = 64+4+2+1024;
+ if (bDelirium)
+ {
+ vrc = 64+32+4+2+1+1024;
+ }
+ int nAng = v78 & 511;
+ if (nAng > 256)
+ {
+ nAng = 512 - nAng;
+ }
+ int nScale = dmulscale32(Cos(nAng), 262144, Sin(nAng), 163840)>>tiltcs;
+ rotatesprite(160<<16, 100<<16, nScale, v78+512, TILTBUFFER, 0, 0, vrc, gViewX0, gViewY0, gViewX1, gViewY1);
+ }
+#ifdef USE_OPENGL
+ else
+ {
+ if (videoGetRenderMode() == REND_POLYMOST && gDeliriumBlur)
+ {
+ if (!bDeliriumOld)
+ {
+ glAccum(GL_LOAD, 1.f);
+ }
+ else
+ {
+ const float fBlur = pow(1.f/3.f, 30.f/g_frameRate);
+ glAccum(GL_MULT, fBlur);
+ glAccum(GL_ACCUM, 1.f-fBlur);
+ glAccum(GL_RETURN, 1.f);
+ }
+ }
+ }
+#endif
+ }
+
+ bDeliriumOld = bDelirium && gDeliriumBlur;
+
+ if (r_usenewaspect)
+ newaspect_enable = 0;
+ renderSetAspect(viewingRange, yxAspect);
+ int nClipDist = gView->pSprite->clipdist<<2;
+ int ve8, vec, vf0, vf4;
+ GetZRange(gView->pSprite, &vf4, &vf0, &vec, &ve8, nClipDist, 0);
+#if 0
+ int tmpSect = nSectnum;
+ if ((vf0 & 0xe000) == 0x4000)
+ {
+ tmpSect = vf0 & (kMaxWalls-1);
+ }
+ int v8 = byte_1CE5C2 > 0 && (sector[tmpSect].ceilingstat&1);
+ if (gWeather.at12d8 > 0 || v8)
+ {
+ gWeather.Draw(cX, cY, cZ, cA, q16horiz + defaultHoriz + deliriumPitch, gWeather.at12d8);
+ if (v8)
+ {
+ gWeather.at12d8 = ClipRange(delta*8+gWeather.at12d8, 0, 4095);
+ }
+ else
+ {
+ gWeather.at12d8 = ClipRange(gWeather.at12d8-delta*64, 0, 4095);
+ }
+ }
+#endif
+ if (gViewPos == 0)
+ {
+ if (gAimReticle)
+ {
+ rotatesprite(160<<16, defaultHoriz<<16, 65536, 0, kCrosshairTile, 0, CROSSHAIR_PAL, 2, gViewX0, gViewY0, gViewX1, gViewY1);
+ }
+ cX = (v4c>>8)+160;
+ cY = (v48>>8)+220+(zDelta>>7);
+ int nShade = sector[nSectnum].floorshade;
+ int nPalette = 0;
+ if (sector[gView->pSprite->sectnum].extra > 0)
+ {
+ sectortype *pSector = §or[gView->pSprite->sectnum];
+ XSECTOR *pXSector = &xsector[pSector->extra];
+ if (pXSector->color)
+ {
+ nPalette = pSector->floorpal;
+ }
+ }
+ WeaponDraw(gView, nShade, cX, cY, nPalette);
+ }
+ if (gViewPos == 0 && gView->pXSprite->burnTime > 60)
+ {
+ viewBurnTime(gView->pXSprite->burnTime);
+ }
+ if (packItemActive(gView, 1))
+ {
+ rotatesprite(0, 0, 65536, 0, 2344, 0, 0, 256+18, gViewX0, gViewY0, gViewX1, gViewY1);
+ rotatesprite(320<<16, 0, 65536, 1024, 2344, 0, 0, 512+22, gViewX0, gViewY0, gViewX1, gViewY1);
+ rotatesprite(0, 200<<16, 65536, 0, 2344, 0, 0, 256+22, gViewX0, gViewY0, gViewX1, gViewY1);
+ rotatesprite(320<<16, 200<<16, 65536, 1024, 2344, 0, 0, 512+18, gViewX0, gViewY0, gViewX1, gViewY1);
+ if (gDetail >= 4)
+ {
+ rotatesprite(15<<16, 3<<16, 65536, 0, 2346, 32, 0, 256+19, gViewX0, gViewY0, gViewX1, gViewY1);
+ rotatesprite(212<<16, 77<<16, 65536, 0, 2347, 32, 0, 512+19, gViewX0, gViewY0, gViewX1, gViewY1);
+ }
+ }
+ if (powerupCheck(gView, 39) > 0)
+ {
+ rotatesprite(0, 200<<16, 65536, 0, 2358, 0, 0, 256+22, gViewX0, gViewY0, gViewX1, gViewY1);
+ rotatesprite(320<<16, 200<<16, 65536, 1024, 2358, 0, 0, 512+18, gViewX0, gViewY0, gViewX1, gViewY1);
+ }
+ if (v4 && gNetPlayers > 1)
+ {
+ DoLensEffect();
+ viewingRange = viewingrange;
+ yxAspect = yxaspect;
+ renderSetAspect(65536, 54613);
+ rotatesprite(280<<16, 35<<16, 53248, 512, 4077, v10, v14, 512+6, gViewX0, gViewY0, gViewX1, gViewY1);
+ rotatesprite(280<<16, 35<<16, 53248, 0, 1683, v10, 0, 512+35, gViewX0, gViewY0, gViewX1, gViewY1);
+ renderSetAspect(viewingRange, yxAspect);
+ }
+ if (powerupCheck(gView, 14) > 0)
+ {
+ nPalette = 4;
+ }
+ else if(powerupCheck(gView, 24) > 0)
+ {
+ nPalette = 1;
+ }
+ else if (gView->at87)
+ {
+ if (gView->nWaterPal)
+ nPalette = gView->nWaterPal;
+ else
+ {
+ if (gView->pXSprite->medium == 1)
+ {
+ nPalette = 1;
+ }
+ else if (gView->pXSprite->medium == 2)
+ {
+ nPalette = 3;
+ }
+ else
+ {
+ nPalette = 2;
+ }
+ }
+ }
+ }
+ if (gViewMode == 4)
+ {
+ gViewMap.sub_25DB0(gView->pSprite);
+ }
+ viewDrawInterface(delta);
+ int zn = ((gView->at6f-gView->at67-(12<<8))>>7)+220;
+ PLAYER *pPSprite = &gPlayer[gMe->pSprite->type-kDudePlayer1];
+ if (pPSprite->at376 == 1)
+ {
+ //static int lastClock;
+ gChoke.sub_84110(160, zn);
+ //if ((gGameClock % 5) == 0 && gGameClock != lastClock)
+ //{
+ // gChoke.at1c(pPSprite);
+ //}
+ //lastClock = gGameClock;
+ }
+ if (byte_1A76C6)
+ {
+ DrawStatSprite(2048, xdim-15, 20);
+ }
+ viewDisplayMessage();
+ CalcFrameRate();
+#if 0
+ if (gShowFrameRate)
+ {
+ int fX, fY;
+ if (gViewMode == 3)
+ {
+ fX = gViewX1;
+ }
+ else
+ {
+ fX = xdim;
+ }
+ if (gViewMode == 3)
+ {
+ fY = gViewY0;
+ }
+ else
+ {
+ fY = 0;
+ }
+ if (gViewMode == 4)
+ {
+ fY += mulscale16(20, yscale);
+ }
+ sprintf(gTempStr, "%3i", gFrameRate);
+ printext256(fX-12, fY, 31, -1, gTempStr, 1);
+ fY += 8;
+ sprintf(gTempStr, "pos=%d,%d,%d", gView->pSprite->x, gView->pSprite->y, gView->pSprite->z);
+ printext256(fX-strlen(gTempStr)*4, fY, 31, -1, gTempStr, 1);
+ }
+#endif
+ viewPrintFPS();
+ if (gPaused)
+ {
+ viewDrawText(1, "PAUSED", 160, 10, 0, 0, 1, 0);
+ }
+ else if (gView != gMe)
+ {
+ sprintf(gTempStr, "] %s [", gProfile[gView->at57].name);
+ viewDrawText(0, gTempStr, 160, 10, 0, 0, 1, 0);
+ }
+ if (errMsg[0])
+ {
+ viewDrawText(0, errMsg, 160, 20, 0, 0, 1, 0);
+ }
+ if (gViewInterpolate)
+ {
+ RestoreInterpolations();
+ }
+ UpdateDacs(nPalette);
+}
+
+int nLoadingScreenTile;
+char pzLoadingScreenText1[256], pzLoadingScreenText2[256], pzLoadingScreenText3[256];
+
+void viewLoadingScreenWide(void)
+{
+ videoClearScreen(0);
+#ifdef USE_OPENGL
+ if ((blood_globalflags&BLOOD_FORCE_WIDELOADSCREEN) || (bLoadScreenCrcMatch && !(usehightile && h_xsize[kLoadScreen])))
+#else
+ if ((blood_globalflags&BLOOD_FORCE_WIDELOADSCREEN) || bLoadScreenCrcMatch)
+#endif
+ {
+ if (yxaspect >= 65536)
+ {
+ rotatesprite(160<<16, 100<<16, 65536, 0, kLoadScreen, 0, 0, 1024+64+8+2, 0, 0, xdim-1, ydim-1);
+ }
+ else
+ {
+ int width = roundscale(xdim, 240, ydim);
+ int nCount = (width+kLoadScreenWideBackWidth-1)/kLoadScreenWideBackWidth;
+ for (int i = 0; i < nCount; i++)
+ {
+ rotatesprite_fs((i*kLoadScreenWideBackWidth)<<16, 0, 65536, 0, kLoadScreenWideBack, 0, 0, 256+64+16+8+2);
+ }
+ rotatesprite_fs((kLoadScreenWideSideWidth>>1)<<16, 200<<15, 65536, 0, kLoadScreenWideLeft, 0, 0, 256+8+2);
+ rotatesprite_fs((320-(kLoadScreenWideSideWidth>>1))<<16, 200<<15, 65536, 0, kLoadScreenWideRight, 0, 0, 512+8+2);
+ rotatesprite_fs(320<<15, 200<<15, 65536, 0, kLoadScreenWideMiddle, 0, 0, 8+2);
+ }
+ }
+ else
+ rotatesprite(160<<16, 100<<16, 65536, 0, kLoadScreen, 0, 0, 64+8+2, 0, 0, xdim-1, ydim-1);
+}
+
+void viewLoadingScreenUpdate(const char *pzText4, int nPercent)
+{
+ int vc;
+ gMenuTextMgr.GetFontInfo(1, NULL, NULL, &vc);
+ if (nLoadingScreenTile == kLoadScreen)
+ viewLoadingScreenWide();
+ else if (nLoadingScreenTile)
+ {
+ videoClearScreen(0);
+ rotatesprite(160<<16, 100<<16, 65536, 0, nLoadingScreenTile, 0, 0, 74, 0, 0, xdim-1, ydim-1);
+ }
+ if (pzLoadingScreenText1[0])
+ {
+ rotatesprite(160<<16, 20<<16, 65536, 0, 2038, -128, 0, 78, 0, 0, xdim-1, ydim-1);
+ viewDrawText(1, pzLoadingScreenText1, 160, 20-vc/2, -128, 0, 1, 1);
+ }
+ if (pzLoadingScreenText2[0])
+ {
+ viewDrawText(1, pzLoadingScreenText2, 160, 50, -128, 0, 1, 1);
+ }
+ if (pzLoadingScreenText3[0])
+ {
+ viewDrawText(1, pzLoadingScreenText3, 160, 70, -128, 0, 1, 1);
+ }
+ if (pzText4)
+ {
+ viewDrawText(3, pzText4, 160, 124, -128, 0, 1, 1);
+ }
+
+ if (nPercent != -1)
+ TileHGauge(2260, 86, 110, nPercent, 100, 0, 131072);
+
+ viewDrawText(3, "Please Wait", 160, 134, -128, 0, 1, 1);
+ scrNextPage();
+}
+
+void viewLoadingScreen(int nTile, const char *pText, const char *pText2, const char *pText3)
+{
+ UpdateDacs(0, true);
+ nLoadingScreenTile = nTile;
+ if (pText)
+ strncpy(pzLoadingScreenText1, pText, 256);
+ else
+ pzLoadingScreenText1[0] = 0;
+ if (pText2)
+ strncpy(pzLoadingScreenText2, pText2, 256);
+ else
+ pzLoadingScreenText2[0] = 0;
+ if (pText3)
+ strncpy(pzLoadingScreenText3, pText3, 256);
+ else
+ pzLoadingScreenText3[0] = 0;
+ viewLoadingScreenUpdate(NULL, -1);
+}
+
+palette_t CrosshairColors = { 255, 255, 255, 0 };
+bool g_isAlterDefaultCrosshair = false;
+
+void viewSetCrosshairColor(int32_t r, int32_t g, int32_t b)
+{
+ if (!g_isAlterDefaultCrosshair)
+ return;
+
+ CrosshairColors.r = r;
+ CrosshairColors.g = g;
+ CrosshairColors.b = b;
+
+ tileLoad(kCrosshairTile);
+
+ if (!waloff[kCrosshairTile])
+ return;
+
+ char *ptr = (char *)waloff[kCrosshairTile];
+
+ int32_t ii = tilesiz[kCrosshairTile].x * tilesiz[kCrosshairTile].y;
+
+ dassert(ii > 0);
+
+ int32_t i = (videoGetRenderMode() == REND_CLASSIC)
+ ? paletteGetClosestColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b)
+ : paletteGetClosestColor(255, 255, 255); // use white in GL so we can tint it to the right color
+
+ do
+ {
+ if (*ptr != 255)
+ *ptr = i;
+ ptr++;
+ } while (--ii);
+
+ paletteMakeLookupTable(CROSSHAIR_PAL, NULL, CrosshairColors.r, CrosshairColors.g, CrosshairColors.b, 1);
+
+#ifdef USE_OPENGL
+ // XXX: this makes us also load all hightile textures tinted with the crosshair color!
+ polytint_t & crosshairtint = hictinting[CROSSHAIR_PAL];
+ crosshairtint.r = CrosshairColors.r;
+ crosshairtint.g = CrosshairColors.g;
+ crosshairtint.b = CrosshairColors.b;
+ crosshairtint.f = HICTINT_USEONART | HICTINT_GRAYSCALE;
+#endif
+ tileInvalidate(kCrosshairTile, -1, -1);
+}
+
+void viewResetCrosshairToDefault(void)
+{
+ paletteFreeLookupTable(CROSSHAIR_PAL);
+ tileLoad(kCrosshairTile);
+}
+
+#define COLOR_RED redcol
+#define COLOR_WHITE whitecol
+
+#define LOW_FPS 60
+#define SLOW_FRAME_TIME 20
+
+#if defined GEKKO
+# define FPS_YOFFSET 16
+#else
+# define FPS_YOFFSET 0
+#endif
+
+#define FPS_COLOR(x) ((x) ? COLOR_RED : COLOR_WHITE)
+
+int32_t gShowFps, gFramePeriod;
+
+void viewPrintFPS(void)
+{
+ char tempbuf[128];
+ static int32_t frameCount;
+ static double cumulativeFrameDelay;
+ static double lastFrameTime;
+ static float lastFPS, minFPS = FLT_MAX, maxFPS;
+ static double minGameUpdate = DBL_MAX, maxGameUpdate;
+
+ double frameTime = timerGetHiTicks();
+ double frameDelay = frameTime - lastFrameTime;
+ cumulativeFrameDelay += frameDelay;
+
+ if (frameDelay >= 0)
+ {
+ int32_t x = (xdim <= 640);
+
+ if (gShowFps)
+ {
+ int32_t chars = Bsprintf(tempbuf, "%.1f ms, %5.1f fps", frameDelay, lastFPS);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+1+FPS_YOFFSET,
+ FPS_COLOR(lastFPS < LOW_FPS), -1, tempbuf, x);
+
+ if (gShowFps > 1)
+ {
+ chars = Bsprintf(tempbuf, "max: %5.1f fps", maxFPS);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+10+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+10+FPS_YOFFSET,
+ FPS_COLOR(maxFPS < LOW_FPS), -1, tempbuf, x);
+
+ chars = Bsprintf(tempbuf, "min: %5.1f fps", minFPS);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+20+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+20+FPS_YOFFSET,
+ FPS_COLOR(minFPS < LOW_FPS), -1, tempbuf, x);
+ }
+ if (gShowFps > 2)
+ {
+ if (g_gameUpdateTime > maxGameUpdate) maxGameUpdate = g_gameUpdateTime;
+ if (g_gameUpdateTime < minGameUpdate) minGameUpdate = g_gameUpdateTime;
+
+ chars = Bsprintf(tempbuf, "Game Update: %2.2f ms + draw: %2.2f ms", g_gameUpdateTime, g_gameUpdateAndDrawTime);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+30+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+30+FPS_YOFFSET,
+ FPS_COLOR(g_gameUpdateAndDrawTime >= SLOW_FRAME_TIME), -1, tempbuf, x);
+
+ chars = Bsprintf(tempbuf, "GU min/max/avg: %5.2f/%5.2f/%5.2f ms", minGameUpdate, maxGameUpdate, g_gameUpdateAvgTime);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+40+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+40+FPS_YOFFSET,
+ FPS_COLOR(maxGameUpdate >= SLOW_FRAME_TIME), -1, tempbuf, x);
+
+ chars = Bsprintf(tempbuf, "bufferjitter: %i", gBufferJitter);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+50+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+50+FPS_YOFFSET,
+ COLOR_WHITE, -1, tempbuf, x);
+#if 0
+ chars = Bsprintf(tempbuf, "G_MoveActors(): %.3e ms", g_moveActorsTime);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+50+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+50+FPS_YOFFSET,
+ COLOR_WHITE, -1, tempbuf, x);
+
+ chars = Bsprintf(tempbuf, "G_MoveWorld(): %.3e ms", g_moveWorldTime);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+60+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+60+FPS_YOFFSET,
+ COLOR_WHITE, -1, tempbuf, x);
+#endif
+ }
+#if 0
+ // lag meter
+ if (g_netClientPeer)
+ {
+ chars = Bsprintf(tempbuf, "%d +- %d ms", (g_netClientPeer->lastRoundTripTime + g_netClientPeer->roundTripTime)/2,
+ (g_netClientPeer->lastRoundTripTimeVariance + g_netClientPeer->roundTripTimeVariance)/2);
+
+ printext256(windowxy2.x-(chars<<(3-x))+1, windowxy1.y+30+2+FPS_YOFFSET, 0, -1, tempbuf, x);
+ printext256(windowxy2.x-(chars<<(3-x)), windowxy1.y+30+1+FPS_YOFFSET, FPS_COLOR(g_netClientPeer->lastRoundTripTime > 200), -1, tempbuf, x);
+ }
+#endif
+ }
+
+ if (cumulativeFrameDelay >= 1000.0)
+ {
+ lastFPS = 1000.f * frameCount / cumulativeFrameDelay;
+ g_frameRate = Blrintf(lastFPS);
+ frameCount = 0;
+ cumulativeFrameDelay = 0.0;
+
+ if (gShowFps > 1)
+ {
+ if (lastFPS > maxFPS) maxFPS = lastFPS;
+ if (lastFPS < minFPS) minFPS = lastFPS;
+
+ static int secondCounter;
+
+ if (++secondCounter >= gFramePeriod)
+ {
+ maxFPS = (lastFPS + maxFPS) * .5f;
+ minFPS = (lastFPS + minFPS) * .5f;
+ maxGameUpdate = (g_gameUpdateTime + maxGameUpdate) * 0.5;
+ minGameUpdate = (g_gameUpdateTime + minGameUpdate) * 0.5;
+ secondCounter = 0;
+ }
+ }
+ }
+ frameCount++;
+ }
+ lastFrameTime = frameTime;
+}
+
+#undef FPS_COLOR
+
+class ViewLoadSave : public LoadSave {
+public:
+ void Load(void);
+ void Save(void);
+};
+
+static ViewLoadSave *myLoadSave;
+
+static int messageTime;
+static char message[256];
+
+void ViewLoadSave::Load(void)
+{
+ Read(&messageTime, sizeof(messageTime));
+ Read(message, sizeof(message));
+ Read(otherMirrorGotpic, sizeof(otherMirrorGotpic));
+ Read(bakMirrorGotpic, sizeof(bakMirrorGotpic));
+ Read(&gScreenTilt, sizeof(gScreenTilt));
+ Read(&deliriumTilt, sizeof(deliriumTilt));
+ Read(&deliriumTurn, sizeof(deliriumTurn));
+ Read(&deliriumPitch, sizeof(deliriumPitch));
+}
+
+void ViewLoadSave::Save(void)
+{
+ Write(&messageTime, sizeof(messageTime));
+ Write(message, sizeof(message));
+ Write(otherMirrorGotpic, sizeof(otherMirrorGotpic));
+ Write(bakMirrorGotpic, sizeof(bakMirrorGotpic));
+ Write(&gScreenTilt, sizeof(gScreenTilt));
+ Write(&deliriumTilt, sizeof(deliriumTilt));
+ Write(&deliriumTurn, sizeof(deliriumTurn));
+ Write(&deliriumPitch, sizeof(deliriumPitch));
+}
+
+void ViewLoadSaveConstruct(void)
+{
+ myLoadSave = new ViewLoadSave();
+}
diff --git a/source/blood/src/view.h b/source/blood/src/view.h
new file mode 100644
index 000000000..232f2b311
--- /dev/null
+++ b/source/blood/src/view.h
@@ -0,0 +1,193 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+
+#include "palette.h"
+#include "common_game.h"
+#include "controls.h"
+#include "messages.h"
+#include "player.h"
+
+enum VIEW_EFFECT {
+ VIEW_EFFECT_0 = 0,
+ VIEW_EFFECT_1,
+ VIEW_EFFECT_2,
+ VIEW_EFFECT_3,
+ VIEW_EFFECT_4,
+ VIEW_EFFECT_5,
+ VIEW_EFFECT_6,
+ VIEW_EFFECT_7,
+ VIEW_EFFECT_8,
+ VIEW_EFFECT_9,
+ VIEW_EFFECT_10,
+ VIEW_EFFECT_11,
+ VIEW_EFFECT_12,
+ VIEW_EFFECT_13,
+ VIEW_EFFECT_14,
+ VIEW_EFFECT_15,
+ VIEW_EFFECT_16,
+ VIEW_EFFECT_17,
+ VIEW_EFFECT_18,
+};
+
+enum VIEWPOS {
+ VIEWPOS_0 = 0,
+ VIEWPOS_1
+};
+
+enum INTERPOLATE_TYPE {
+ INTERPOLATE_TYPE_INT = 0,
+ INTERPOLATE_TYPE_SHORT,
+};
+
+#define CROSSHAIR_PAL (MAXPALOOKUPS-RESERVEDPALS-1)
+#define kCrosshairTile 2319
+#define kLoadScreen 2049
+#define kLoadScreenCRC -2051908571
+#define kLoadScreenWideBackWidth 256
+#define kLoadScreenWideSideWidth 128
+#define kLoadScreenWideBack 9216
+#define kLoadScreenWideLeft 9217
+#define kLoadScreenWideRight 9218
+#define kLoadScreenWideMiddle 9219
+
+struct FONT {
+ int tile, xSize, ySize, space;
+};
+
+extern int gZoom;
+extern FONT gFont[5];
+extern int gViewMode;
+extern VIEWPOS gViewPos;
+extern int gViewIndex;
+extern int gScreenTilt;
+extern int deliriumTilt, deliriumTurn, deliriumPitch;
+extern int gScreenTiltO, deliriumTurnO, deliriumPitchO;
+extern int gShowFrameRate;
+extern char gInterpolateSprite[512];
+extern char gInterpolateWall[1024];
+extern char gInterpolateSector[128];
+extern LOCATION gPrevSpriteLoc[kMaxSprites];
+extern int gViewSize;
+extern CGameMessageMgr gGameMessageMgr;
+extern int gViewXCenter, gViewYCenter;
+extern int gViewX0, gViewY0, gViewX1, gViewY1;
+extern int gViewX0S, gViewY0S, gViewX1S, gViewY1S;
+extern palette_t CrosshairColors;
+extern bool g_isAlterDefaultCrosshair;
+extern int32_t r_maxfps;
+extern int32_t r_maxfpsoffset;
+extern double g_frameDelay;
+extern float r_ambientlight, r_ambientlightrecip;
+extern int gLastPal;
+extern int32_t gShowFps, gFramePeriod;
+
+
+static inline double calcFrameDelay(int maxFPS) { return maxFPS ? ((double)timerGetFreqU64() / (double)(maxFPS)) : 0.0; }
+
+void viewGetFontInfo(int id, const char *unk1, int *pXSize, int *pYSize);
+void viewUpdatePages(void);
+void viewToggle(int viewMode);
+void viewInitializePrediction(void);
+void viewUpdatePrediction(GINPUT *pInput);
+void sub_158B4(PLAYER *pPlayer);
+void fakeProcessInput(PLAYER *pPlayer, GINPUT *pInput);
+void fakePlayerProcess(PLAYER *pPlayer, GINPUT *pInput);
+void fakeMoveDude(spritetype *pSprite);
+void fakeActAirDrag(spritetype *pSprite, int num);
+void fakeActProcessSprites(void);
+void viewCorrectPrediction(void);
+void viewBackupView(int nPlayer);
+void viewClearInterpolations(void);
+void viewAddInterpolation(void *data, INTERPOLATE_TYPE type);
+void CalcInterpolations(void);
+void RestoreInterpolations(void);
+void viewDrawText(int nFont, const char *pString, int x, int y, int nShade, int nPalette, int position, char shadow, unsigned int nStat = 0, uint8_t alpha = 0);
+void viewTileSprite(int nTile, int nShade, int nPalette, int x1, int y1, int x2, int y2);
+void InitStatusBar(void);
+void DrawStatSprite(int nTile, int x, int y, int nShade = 0, int nPalette = 0, unsigned int nStat = 0, int nScale = 65536);
+void DrawStatMaskedSprite(int nTile, int x, int y, int nShade = 0, int nPalette = 0, unsigned int nStat = 0, int nScale = 65536);
+void DrawStatNumber(const char *pFormat, int nNumber, int nTile, int x, int y, int nShade, int nPalette, unsigned int nStat = 0, int nScale = 65536);
+void TileHGauge(int nTile, int x, int y, int nMult, int nDiv, int nStat = 0, int nScale = 65536);
+void viewDrawPack(PLAYER *pPlayer, int x, int y);
+void DrawPackItemInStatusBar(PLAYER *pPlayer, int x, int y, int x2, int y2, int nStat = 0);
+void UpdateStatusBar(int arg);
+void viewInit(void);
+void viewResizeView(int size);
+void UpdateFrame(void);
+void viewDrawInterface(int arg);
+uspritetype *viewInsertTSprite(int nSector, int nStatnum, uspritetype *pSprite);
+uspritetype *viewAddEffect(int nTSprite, VIEW_EFFECT nViewEffect);
+void viewProcessSprites(int cX, int cY, int cZ);
+void CalcOtherPosition(spritetype *pSprite, int *pX, int *pY, int *pZ, int *vsectnum, int nAng, int zm);
+void CalcPosition(spritetype *pSprite, int *pX, int *pY, int *pZ, int *vsectnum, int nAng, int zm);
+void viewSetMessage(const char *pMessage);
+void viewDisplayMessage(void);
+void viewSetErrorMessage(const char *pMessage);
+void DoLensEffect(void);
+void UpdateDacs(int nPalette, bool bNoTint = false);
+int viewFPSLimit(void);
+void viewDrawScreen(void);
+void viewLoadingScreenWide(void);
+void viewLoadingScreenUpdate(const char *pzText4 = NULL, int nPercent = -1);
+void viewLoadingScreen(int nTile, const char *pText, const char *pText2, const char *pText3);
+void viewUpdateDelirium(void);
+void viewUpdateShake(void);
+void viewSetCrosshairColor(int32_t r, int32_t g, int32_t b);
+void viewResetCrosshairToDefault(void);
+void viewPrintFPS(void);
+
+
+inline void viewInterpolateSector(int nSector, sectortype *pSector)
+{
+ if (!TestBitString(gInterpolateSector, nSector))
+ {
+ viewAddInterpolation(&pSector->floorz, INTERPOLATE_TYPE_INT);
+ viewAddInterpolation(&pSector->ceilingz, INTERPOLATE_TYPE_INT);
+ viewAddInterpolation(&pSector->floorheinum, INTERPOLATE_TYPE_SHORT);
+ SetBitString(gInterpolateSector, nSector);
+ }
+}
+
+inline void viewInterpolateWall(int nWall, walltype *pWall)
+{
+ if (!TestBitString(gInterpolateWall, nWall))
+ {
+ viewAddInterpolation(&pWall->x, INTERPOLATE_TYPE_INT);
+ viewAddInterpolation(&pWall->y, INTERPOLATE_TYPE_INT);
+ SetBitString(gInterpolateWall, nWall);
+ }
+}
+
+inline void viewBackupSpriteLoc(int nSprite, spritetype *pSprite)
+{
+ if (!TestBitString(gInterpolateSprite, nSprite))
+ {
+ LOCATION *pPrevLoc = &gPrevSpriteLoc[nSprite];
+ pPrevLoc->x = pSprite->x;
+ pPrevLoc->y = pSprite->y;
+ pPrevLoc->z = pSprite->z;
+ pPrevLoc->ang = pSprite->ang;
+ SetBitString(gInterpolateSprite, nSprite);
+ }
+}
diff --git a/source/blood/src/warp.cpp b/source/blood/src/warp.cpp
new file mode 100644
index 000000000..ce7a0ca3a
--- /dev/null
+++ b/source/blood/src/warp.cpp
@@ -0,0 +1,322 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include "build.h"
+#include "compat.h"
+#include "common_game.h"
+#include "blood.h"
+#include "db.h"
+#include "gameutil.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "view.h"
+#include "warp.h"
+
+ZONE gStartZone[8];
+ZONE gStartZoneTeam1[8];
+ZONE gStartZoneTeam2[8];
+
+bool gTeamsSpawnUsed = false;
+
+void warpInit(void)
+{
+ for (int i = 0; i < kMaxSectors; i++)
+ {
+ gUpperLink[i] = -1;
+ gLowerLink[i] = -1;
+ }
+ int team1 = 0; int team2 = 0; // increment if team start positions specified.
+ for (int nSprite = 0; nSprite < kMaxSprites; nSprite++)
+ {
+ if (sprite[nSprite].statnum < kMaxStatus)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ if (nXSprite > 0)
+ {
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ switch (pSprite->type)
+ {
+ case 1:
+ if (gGameOptions.nGameType < 2 && pXSprite->data1 >= 0 && pXSprite->data1 < 8)
+ {
+ ZONE *pZone = &gStartZone[pXSprite->data1];
+ pZone->x = pSprite->x;
+ pZone->y = pSprite->y;
+ pZone->z = pSprite->z;
+ pZone->sectnum = pSprite->sectnum;
+ pZone->ang = pSprite->ang;
+ }
+ DeleteSprite(nSprite);
+ break;
+ case 2:
+ if (pXSprite->data1 >= 0 && pXSprite->data2 < 8) {
+ if (gGameOptions.nGameType >= 2)
+ {
+ // default if BB or teams without data2 specified
+ ZONE* pZone = &gStartZone[pXSprite->data1];
+ pZone->x = pSprite->x;
+ pZone->y = pSprite->y;
+ pZone->z = pSprite->z;
+ pZone->sectnum = pSprite->sectnum;
+ pZone->ang = pSprite->ang;
+
+ // By NoOne: fill player spawn position according team of player in TEAMS mode.
+ if (gGameOptions.nGameType == 3) {
+ if (pXSprite->data2 == 1) {
+ pZone = &gStartZoneTeam1[team1];
+ pZone->x = pSprite->x;
+ pZone->y = pSprite->y;
+ pZone->z = pSprite->z;
+ pZone->sectnum = pSprite->sectnum;
+ pZone->ang = pSprite->ang;
+ team1++;
+
+ } else if (pXSprite->data2 == 2) {
+ pZone = &gStartZoneTeam2[team2];
+ pZone->x = pSprite->x;
+ pZone->y = pSprite->y;
+ pZone->z = pSprite->z;
+ pZone->sectnum = pSprite->sectnum;
+ pZone->ang = pSprite->ang;
+ team2++;
+ }
+ }
+ }
+ DeleteSprite(nSprite);
+ }
+ break;
+ case 7:
+ gUpperLink[pSprite->sectnum] = nSprite;
+ pSprite->cstat |= 32768;
+ pSprite->cstat &= ~257;
+ break;
+ case 6:
+ gLowerLink[pSprite->sectnum] = nSprite;
+ pSprite->cstat |= 32768;
+ pSprite->cstat &= ~257;
+ break;
+ case 9:
+ case 11:
+ case 13:
+ gUpperLink[pSprite->sectnum] = nSprite;
+ pSprite->cstat |= 32768;
+ pSprite->cstat &= ~257;
+ pSprite->z = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ break;
+ case 10:
+ case 12:
+ case 14:
+ gLowerLink[pSprite->sectnum] = nSprite;
+ pSprite->cstat |= 32768;
+ pSprite->cstat &= ~257;
+ pSprite->z = getceilzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ break;
+ }
+ }
+ }
+ }
+ // check if there is enough start positions for teams, if any used
+ if (team1 > 0 || team2 > 0) {
+ gTeamsSpawnUsed = true;
+ //if (team1 < kMaxPlayers / 2 || team2 < kMaxPlayers / 2)
+ //_ShowMessageWindow("At least 4 spawn positions for each team is recommended.");
+ }
+
+ for (int i = 0; i < kMaxSectors; i++)
+ {
+ int nSprite = gUpperLink[i];
+ if (nSprite >= 0)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ int nXSprite = pSprite->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ int nLink = pXSprite->data1;
+ for (int j = 0; j < kMaxSectors; j++)
+ {
+ int nSprite2 = gLowerLink[j];
+ if (nSprite2 >= 0)
+ {
+ spritetype *pSprite2 = &sprite[nSprite2];
+ int nXSprite = pSprite2->extra;
+ dassert(nXSprite > 0 && nXSprite < kMaxXSprites);
+ XSPRITE *pXSprite2 = &xsprite[nXSprite];
+ if (pXSprite2->data1 == nLink)
+ {
+ pSprite->owner = gLowerLink[j];
+ pSprite2->owner = gUpperLink[i];
+ }
+ }
+ }
+ }
+ }
+}
+
+int CheckLink(spritetype *pSprite)
+{
+ int nSector = pSprite->sectnum;
+ int nUpper = gUpperLink[nSector];
+ int nLower = gLowerLink[nSector];
+ if (nUpper >= 0)
+ {
+ spritetype *pUpper = &sprite[nUpper];
+ int z;
+ if (pUpper->type == 7)
+ z = pUpper->z;
+ else
+ z = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ if (z <= pSprite->z)
+ {
+ nLower = pUpper->owner;
+ dassert(nLower >= 0 && nLower < kMaxSprites);
+ spritetype *pLower = &sprite[nLower];
+ dassert(pLower->sectnum >= 0 && pLower->sectnum < kMaxSectors);
+ ChangeSpriteSect(pSprite->index, pLower->sectnum);
+ pSprite->x += pLower->x-pUpper->x;
+ pSprite->y += pLower->y-pUpper->y;
+ int z2;
+ if (pLower->type == 6)
+ z2 = pLower->z;
+ else
+ z2 = getceilzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ pSprite->z += z2-z;
+ ClearBitString(gInterpolateSprite, pSprite->index);
+ return pUpper->type;
+ }
+ }
+ if (nLower >= 0)
+ {
+ spritetype *pLower = &sprite[nLower];
+ int z;
+ if (pLower->type == 6)
+ z = pLower->z;
+ else
+ z = getceilzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ if (z >= pSprite->z)
+ {
+ nUpper = pLower->owner;
+ dassert(nUpper >= 0 && nUpper < kMaxSprites);
+ spritetype *pUpper = &sprite[nUpper];
+ dassert(pUpper->sectnum >= 0 && pUpper->sectnum < kMaxSectors);
+ ChangeSpriteSect(pSprite->index, pUpper->sectnum);
+ pSprite->x += pUpper->x-pLower->x;
+ pSprite->y += pUpper->y-pLower->y;
+ int z2;
+ if (pUpper->type == 7)
+ z2 = pUpper->z;
+ else
+ z2 = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y);
+ pSprite->z += z2-z;
+ ClearBitString(gInterpolateSprite, pSprite->index);
+ return pLower->type;
+ }
+ }
+ return 0;
+}
+
+int CheckLink(int *x, int *y, int *z, int *nSector)
+{
+ int nUpper = gUpperLink[*nSector];
+ int nLower = gLowerLink[*nSector];
+ if (nUpper >= 0)
+ {
+ spritetype *pUpper = &sprite[nUpper];
+ int z1;
+ if (pUpper->type == 7)
+ z1 = pUpper->z;
+ else
+ z1 = getflorzofslope(*nSector, *x, *y);
+ if (z1 <= *z)
+ {
+ nLower = pUpper->owner;
+ dassert(nLower >= 0 && nLower < kMaxSprites);
+ spritetype *pLower = &sprite[nLower];
+ dassert(pLower->sectnum >= 0 && pLower->sectnum < kMaxSectors);
+ *nSector = pLower->sectnum;
+ *x += pLower->x-pUpper->x;
+ *y += pLower->y-pUpper->y;
+ int z2;
+ if (pUpper->type == 6)
+ z2 = pLower->z;
+ else
+ z2 = getceilzofslope(*nSector, *x, *y);
+ *z += z2-z1;
+ return pUpper->type;
+ }
+ }
+ if (nLower >= 0)
+ {
+ spritetype *pLower = &sprite[nLower];
+ int z1;
+ if (pLower->type == 6)
+ z1 = pLower->z;
+ else
+ z1 = getceilzofslope(*nSector, *x, *y);
+ if (z1 >= *z)
+ {
+ nUpper = pLower->owner;
+ dassert(nUpper >= 0 && nUpper < kMaxSprites);
+ spritetype *pUpper = &sprite[nUpper];
+ dassert(pUpper->sectnum >= 0 && pUpper->sectnum < kMaxSectors);
+ *nSector = pUpper->sectnum;
+ *x += pUpper->x-pLower->x;
+ *y += pUpper->y-pLower->y;
+ int z2;
+ if (pLower->type == 7)
+ z2 = pUpper->z;
+ else
+ z2 = getflorzofslope(*nSector, *x, *y);
+ *z += z2-z1;
+ return pLower->type;
+ }
+ }
+ return 0;
+}
+
+class WarpLoadSave : public LoadSave
+{
+public:
+ virtual void Load();
+ virtual void Save();
+};
+
+void WarpLoadSave::Load()
+{
+ Read(gStartZone, sizeof(gStartZone));
+ Read(gUpperLink, sizeof(gUpperLink));
+ Read(gLowerLink, sizeof(gLowerLink));
+}
+
+void WarpLoadSave::Save()
+{
+ Write(gStartZone, sizeof(gStartZone));
+ Write(gUpperLink, sizeof(gUpperLink));
+ Write(gLowerLink, sizeof(gLowerLink));
+}
+
+static WarpLoadSave *myLoadSave;
+
+void WarpLoadSaveConstruct(void)
+{
+ myLoadSave = new WarpLoadSave();
+}
diff --git a/source/blood/src/warp.h b/source/blood/src/warp.h
new file mode 100644
index 000000000..48d4c0641
--- /dev/null
+++ b/source/blood/src/warp.h
@@ -0,0 +1,37 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "common_game.h"
+
+struct ZONE {
+ int x, y, z;
+ short sectnum, ang;
+};
+extern ZONE gStartZone[8];
+extern ZONE gStartZoneTeam1[8];
+extern ZONE gStartZoneTeam2[8];
+extern bool gTeamsSpawnUsed;
+
+void warpInit(void);
+int CheckLink(spritetype *pSprite);
+int CheckLink(int *x, int *y, int *z, int *nSector);
diff --git a/source/blood/src/weapon.cpp b/source/blood/src/weapon.cpp
new file mode 100644
index 000000000..cb57638fb
--- /dev/null
+++ b/source/blood/src/weapon.cpp
@@ -0,0 +1,2473 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#include
+#include
+#include
+#include "compat.h"
+#include "build.h"
+#include "mmulti.h"
+#include "common_game.h"
+#include "actor.h"
+#include "blood.h"
+#include "db.h"
+#include "callback.h"
+#include "config.h"
+#include "eventq.h"
+#include "fx.h"
+#include "gameutil.h"
+#include "globals.h"
+#include "levels.h"
+#include "loadsave.h"
+#include "player.h"
+#include "qav.h"
+#include "resource.h"
+#include "seq.h"
+#include "sfx.h"
+#include "sound.h"
+#include "trig.h"
+#include "triggers.h"
+#include "view.h"
+
+void FirePitchfork(int, PLAYER *pPlayer);
+void FireSpray(int, PLAYER *pPlayer);
+void ThrowCan(int, PLAYER *pPlayer);
+void DropCan(int, PLAYER *pPlayer);
+void ExplodeCan(int, PLAYER *pPlayer);
+void ThrowBundle(int, PLAYER *pPlayer);
+void DropBundle(int, PLAYER *pPlayer);
+void ExplodeBundle(int, PLAYER *pPlayer);
+void ThrowProx(int, PLAYER *pPlayer);
+void DropProx(int, PLAYER *pPlayer);
+void ThrowRemote(int, PLAYER *pPlayer);
+void DropRemote(int, PLAYER *pPlayer);
+void FireRemote(int, PLAYER *pPlayer);
+void FireShotgun(int nTrigger, PLAYER *pPlayer);
+void EjectShell(int, PLAYER *pPlayer);
+void FireTommy(int nTrigger, PLAYER *pPlayer);
+void FireSpread(int nTrigger, PLAYER *pPlayer);
+void AltFireSpread(int nTrigger, PLAYER *pPlayer);
+void AltFireSpread2(int nTrigger, PLAYER *pPlayer);
+void FireFlare(int nTrigger, PLAYER *pPlayer);
+void AltFireFlare(int nTrigger, PLAYER *pPlayer);
+void FireVoodoo(int nTrigger, PLAYER *pPlayer);
+void AltFireVoodoo(int nTrigger, PLAYER *pPlayer);
+void DropVoodoo(int nTrigger, PLAYER *pPlayer);
+void FireTesla(int nTrigger, PLAYER *pPlayer);
+void AltFireTesla(int nTrigger, PLAYER *pPlayer);
+void FireNapalm(int nTrigger, PLAYER *pPlayer);
+void FireNapalm2(int nTrigger, PLAYER *pPlayer);
+void AltFireNapalm(int nTrigger, PLAYER *pPlayer);
+void FireLifeLeech(int nTrigger, PLAYER *pPlayer);
+void AltFireLifeLeech(int nTrigger, PLAYER *pPlayer);
+void FireBeast(int nTrigger, PLAYER * pPlayer);
+
+typedef void(*QAVTypeCast)(int, void *);
+
+int nClientFirePitchfork = qavRegisterClient((QAVTypeCast)FirePitchfork);
+int nClientFireSpray = qavRegisterClient((QAVTypeCast)FireSpray);
+int nClientThrowCan = qavRegisterClient((QAVTypeCast)ThrowCan);
+int nClientDropCan = qavRegisterClient((QAVTypeCast)DropCan);
+int nClientExplodeCan = qavRegisterClient((QAVTypeCast)ExplodeCan);
+int nClientThrowBundle = qavRegisterClient((QAVTypeCast)ThrowBundle);
+int nClientDropBundle = qavRegisterClient((QAVTypeCast)DropBundle);
+int nClientExplodeBundle = qavRegisterClient((QAVTypeCast)ExplodeBundle);
+int nClientThrowProx = qavRegisterClient((QAVTypeCast)ThrowProx);
+int nClientDropProx = qavRegisterClient((QAVTypeCast)DropProx);
+int nClientThrowRemote = qavRegisterClient((QAVTypeCast)ThrowRemote);
+int nClientDropRemote = qavRegisterClient((QAVTypeCast)DropRemote);
+int nClientFireRemote = qavRegisterClient((QAVTypeCast)FireRemote);
+int nClientFireShotgun = qavRegisterClient((QAVTypeCast)FireShotgun);
+int nClientEjectShell = qavRegisterClient((QAVTypeCast)EjectShell);
+int nClientFireTommy = qavRegisterClient((QAVTypeCast)FireTommy);
+int nClientAltFireSpread2 = qavRegisterClient((QAVTypeCast)AltFireSpread2);
+int nClientFireSpread = qavRegisterClient((QAVTypeCast)FireSpread);
+int nClientAltFireSpread = qavRegisterClient((QAVTypeCast)AltFireSpread);
+int nClientFireFlare = qavRegisterClient((QAVTypeCast)FireFlare);
+int nClientAltFireFlare = qavRegisterClient((QAVTypeCast)AltFireFlare);
+int nClientFireVoodoo = qavRegisterClient((QAVTypeCast)FireVoodoo);
+int nClientAltFireVoodoo = qavRegisterClient((QAVTypeCast)AltFireVoodoo);
+int nClientFireTesla = qavRegisterClient((QAVTypeCast)FireTesla);
+int nClientAltFireTesla = qavRegisterClient((QAVTypeCast)AltFireTesla);
+int nClientFireNapalm = qavRegisterClient((QAVTypeCast)FireNapalm);
+int nClientFireNapalm2 = qavRegisterClient((QAVTypeCast)FireNapalm2);
+int nClientFireLifeLeech = qavRegisterClient((QAVTypeCast)FireLifeLeech);
+int nClientFireBeast = qavRegisterClient((QAVTypeCast)FireBeast);
+int nClientAltFireLifeLeech = qavRegisterClient((QAVTypeCast)AltFireLifeLeech);
+int nClientDropVoodoo = qavRegisterClient((QAVTypeCast)DropVoodoo);
+int nClientAltFireNapalm = qavRegisterClient((QAVTypeCast)AltFireNapalm);
+
+#define kQAVEnd 125
+
+QAV *weaponQAV[kQAVEnd];
+
+void QAV::PlaySound(int nSound)
+{
+ sndStartSample(nSound, -1, -1, 0);
+}
+
+void QAV::PlaySound3D(spritetype *pSprite, int nSound, int a3, int a4)
+{
+ sfxPlay3DSound(pSprite, nSound, a3, a4);
+}
+
+char sub_4B1A4(PLAYER *pPlayer)
+{
+ switch (pPlayer->atbd)
+ {
+ case 7:
+ switch (pPlayer->atc3)
+ {
+ case 5:
+ case 6:
+ return 1;
+ }
+ break;
+ case 6:
+ switch (pPlayer->atc3)
+ {
+ case 4:
+ case 5:
+ case 6:
+ return 1;
+ }
+ break;
+ }
+ return 0;
+}
+
+char BannedUnderwater(int nWeapon)
+{
+ return nWeapon == 7 || nWeapon == 6;
+}
+
+char sub_4B1FC(PLAYER *pPlayer, int a2, int a3, int a4)
+{
+ if (gInfiniteAmmo)
+ return 1;
+ if (a3 == -1)
+ return 1;
+ if (a2 == 12 && pPlayer->atc7 == 11 && pPlayer->atc3 == 11)
+ return 1;
+ if (a2 == 9 && pPlayer->pXSprite->health > 0)
+ return 1;
+ return pPlayer->at181[a3] >= a4;
+}
+
+char CheckAmmo(PLAYER *pPlayer, int a2, int a3)
+{
+ if (gInfiniteAmmo)
+ return 1;
+ if (a2 == -1)
+ return 1;
+ if (pPlayer->atbd == 12 && pPlayer->atc7 == 11 && pPlayer->atc3 == 11)
+ return 1;
+ if (pPlayer->atbd == 9 && pPlayer->pXSprite->health >= (a3<<4))
+ return 1;
+ return pPlayer->at181[a2] >= a3;
+}
+
+char sub_4B2C8(PLAYER *pPlayer, int a2, int a3)
+{
+ if (gInfiniteAmmo)
+ return 1;
+ if (a2 == -1)
+ return 1;
+ return pPlayer->at181[a2] >= a3;
+}
+
+void SpawnBulletEject(PLAYER *pPlayer, int a2, int a3)
+{
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][pPlayer->at2f];
+ pPlayer->at67 = pPlayer->pSprite->z-pPosture->at24;
+ int dz = pPlayer->at6f-(pPlayer->at6f-pPlayer->at67)/2;
+ sub_74818(pPlayer->pSprite, dz, a2, a3);
+}
+
+void SpawnShellEject(PLAYER *pPlayer, int a2, int a3)
+{
+ POSTURE *pPosture = &gPosture[pPlayer->at5f][pPlayer->at2f];
+ pPlayer->at67 = pPlayer->pSprite->z-pPosture->at24;
+ int t = pPlayer->at6f - pPlayer->at67;
+ int dz = pPlayer->at6f-t+(t>>2);
+ sub_74A18(pPlayer->pSprite, dz, a2, a3);
+}
+
+void WeaponInit(void)
+{
+ for (int i = 0; i < kQAVEnd; i++)
+ {
+ DICTNODE *hRes = gSysRes.Lookup(i, "QAV");
+ if (!hRes)
+ ThrowError("Could not load QAV %d\n", i);
+ weaponQAV[i] = (QAV*)gSysRes.Lock(hRes);
+ weaponQAV[i]->nSprite = -1;
+ }
+}
+
+void WeaponDraw(PLAYER *pPlayer, int a2, int a3, int a4, int a5)
+{
+ dassert(pPlayer != NULL);
+ if (pPlayer->at26 == -1)
+ return;
+ QAV *pQAV = weaponQAV[pPlayer->at26];
+ int v4;
+ if (pPlayer->atbf == 0)
+ v4 = gGameClock%pQAV->at10;
+ else
+ v4 = pQAV->at10-pPlayer->atbf;
+ pQAV->x = a3;
+ pQAV->y = a4;
+ int flags = 2;
+ int nInv = powerupCheck(pPlayer, 13);
+ if (nInv >= 120*8 || (nInv != 0 && (gGameClock&32)))
+ {
+ a2 = -128;
+ flags |= 1;
+ }
+ pQAV->Draw(v4, flags, a2, a5);
+}
+
+void WeaponPlay(PLAYER *pPlayer)
+{
+ dassert(pPlayer != NULL);
+ if (pPlayer->at26 == -1)
+ return;
+ QAV *pQAV = weaponQAV[pPlayer->at26];
+ pQAV->nSprite = pPlayer->pSprite->index;
+ int nTicks = pQAV->at10 - pPlayer->atbf;
+ pQAV->Play(nTicks-4, nTicks, pPlayer->at2a, pPlayer);
+}
+
+void StartQAV(PLAYER *pPlayer, int nWeaponQAV, int a3 = -1, char a4 = 0)
+{
+ dassert(nWeaponQAV < kQAVEnd);
+ pPlayer->at26 = nWeaponQAV;
+ pPlayer->atbf = weaponQAV[nWeaponQAV]->at10;
+ pPlayer->at2a = a3;
+ pPlayer->at1b1 = a4;
+ weaponQAV[nWeaponQAV]->Preload();
+ WeaponPlay(pPlayer);
+ pPlayer->atbf -= 4;
+}
+
+struct WEAPONTRACK
+{
+ int at0; // x aim speed
+ int at4; // y aim speed
+ int at8; // angle range
+ int atc;
+ int at10; // predict
+ bool bIsProjectile;
+};
+
+WEAPONTRACK gWeaponTrack[] = {
+ { 0, 0, 0, 0, 0, false },
+ { 0x6000, 0x6000, 0x71, 0x55, 0x111111, false },
+ { 0x8000, 0x8000, 0x71, 0x55, 0x2aaaaa, true },
+ { 0x10000, 0x10000, 0x38, 0x1c, 0, false },
+ { 0x6000, 0x8000, 0x38, 0x1c, 0, false },
+ { 0x6000, 0x6000, 0x38, 0x1c, 0x2aaaaa, true },
+ { 0x6000, 0x6000, 0x71, 0x55, 0, true },
+ { 0x6000, 0x6000, 0x71, 0x38, 0, true },
+ { 0x8000, 0x10000, 0x71, 0x55, 0x255555, true },
+ { 0x10000, 0x10000, 0x71, 0, 0, true },
+ { 0x10000, 0x10000, 0xaa, 0, 0, false },
+ { 0x6000, 0x6000, 0x71, 0x55, 0, true },
+ { 0x6000, 0x6000, 0x71, 0x55, 0, true },
+ { 0x6000, 0x6000, 0x71, 0x55, 0, false },
+};
+
+void UpdateAimVector(PLAYER * pPlayer)
+{
+ short nSprite;
+ spritetype *pSprite;
+ dassert(pPlayer != NULL);
+ spritetype *pPSprite = pPlayer->pSprite;
+ int x = pPSprite->x;
+ int y = pPSprite->y;
+ int z = pPlayer->at6f;
+ Aim aim;
+ aim.dx = Cos(pPSprite->ang)>>16;
+ aim.dy = Sin(pPSprite->ang)>>16;
+ aim.dz = pPlayer->at83;
+ WEAPONTRACK *pWeaponTrack = &gWeaponTrack[pPlayer->atbd];
+ int nTarget = -1;
+ pPlayer->at1da = 0;
+ if (gProfile[pPlayer->at57].nAutoAim == 1 || (gProfile[pPlayer->at57].nAutoAim == 2 && !pWeaponTrack->bIsProjectile) || pPlayer->atbd == 10 || pPlayer->atbd == 9)
+ {
+ int nClosest = 0x7fffffff;
+ for (nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ pSprite = &sprite[nSprite];
+ if (pSprite == pPSprite)
+ continue;
+ if (pSprite->hitag&32)
+ continue;
+ if (!(pSprite->hitag&8))
+ continue;
+ int x2 = pSprite->x;
+ int y2 = pSprite->y;
+ int z2 = pSprite->z;
+ int nDist = approxDist(x2-x, y2-y);
+ if (nDist == 0 || nDist > 51200)
+ continue;
+ if (pWeaponTrack->at10)
+ {
+ int t = divscale(nDist,pWeaponTrack->at10, 12);
+ x2 += (xvel[nSprite]*t)>>12;
+ y2 += (yvel[nSprite]*t)>>12;
+ z2 += (zvel[nSprite]*t)>>8;
+ }
+ int lx = x + mulscale30(Cos(pPSprite->ang), nDist);
+ int ly = y + mulscale30(Sin(pPSprite->ang), nDist);
+ int lz = z + mulscale(pPlayer->at83, nDist, 10);
+ int zRange = mulscale(9460, nDist, 10);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (lz-zRange>bottom || lz+zRangeang+1024)&2047)-1024) > pWeaponTrack->at8)
+ continue;
+ if (pPlayer->at1da < 16 && cansee(x,y,z,pPSprite->sectnum,x2,y2,z2,pSprite->sectnum))
+ pPlayer->at1de[pPlayer->at1da++] = nSprite;
+ // Inlined?
+ int dz = (lz-z2)>>8;
+ int dy = (ly-y2)>>4;
+ int dx = (lx-x2)>>4;
+ int nDist2 = ksqrt(dx*dx+dy*dy+dz*dz);
+ if (nDist2 >= nClosest)
+ continue;
+ DUDEINFO *pDudeInfo = &dudeInfo[pSprite->type-kDudeBase];
+ int center = (pSprite->yrepeat*pDudeInfo->aimHeight)<<2;
+ int dzCenter = (z2-center)-z;
+ if (cansee(x, y, z, pPSprite->sectnum, x2, y2, z2, pSprite->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(angle)>>16;
+ aim.dy = Sin(angle)>>16;
+ aim.dz = divscale(dzCenter, nDist, 10);
+ nTarget = nSprite;
+ }
+ }
+ if (pWeaponTrack->atc > 0)
+ {
+ for (nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ pSprite = &sprite[nSprite];
+ if (!(pSprite->hitag&8))
+ continue;
+ int x2 = pSprite->x;
+ int y2 = pSprite->y;
+ int z2 = pSprite->z;
+ int dx = x2-x;
+ int dy = y2-y;
+ int dz = z2-z;
+ int nDist = approxDist(dx, dy);
+ if (nDist == 0 || nDist > 51200)
+ continue;
+ int lx = x + mulscale30(Cos(pPSprite->ang), nDist);
+ int ly = y + mulscale30(Sin(pPSprite->ang), nDist);
+ int lz = z + mulscale(pPlayer->at83, nDist, 10);
+ int zRange = mulscale10(9460, nDist);
+ int top, bottom;
+ GetSpriteExtents(pSprite, &top, &bottom);
+ if (lz-zRange>bottom || lz+zRangeang+1024)&2047)-1024) > pWeaponTrack->atc)
+ continue;
+ if (pPlayer->at1da < 16 && cansee(x,y,z,pPSprite->sectnum,pSprite->x,pSprite->y,pSprite->z,pSprite->sectnum))
+ pPlayer->at1de[pPlayer->at1da++] = nSprite;
+ // Inlined?
+ int dz2 = (lz-z2)>>8;
+ int dy2 = (ly-y2)>>4;
+ int dx2 = (lx-x2)>>4;
+ int nDist2 = ksqrt(dx2*dx2+dy2*dy2+dz2*dz2);
+ if (nDist2 >= nClosest)
+ continue;
+ if (cansee(x, y, z, pPSprite->sectnum, pSprite->x, pSprite->y, pSprite->z, pSprite->sectnum))
+ {
+ nClosest = nDist2;
+ aim.dx = Cos(angle)>>16;
+ aim.dy = Sin(angle)>>16;
+ aim.dz = divscale(dz, nDist, 10);
+ nTarget = nSprite;
+ }
+ }
+ }
+ }
+ Aim aim2;
+ aim2 = aim;
+ RotateVector((int*)&aim2.dx, (int*)&aim2.dy, -pPSprite->ang);
+ aim2.dz -= pPlayer->at83;
+ pPlayer->at1ca.dx = interpolate(pPlayer->at1ca.dx, aim2.dx, pWeaponTrack->at0);
+ pPlayer->at1ca.dy = interpolate(pPlayer->at1ca.dy, aim2.dy, pWeaponTrack->at0);
+ pPlayer->at1ca.dz = interpolate(pPlayer->at1ca.dz, aim2.dz, pWeaponTrack->at4);
+ pPlayer->at1be = pPlayer->at1ca;
+ RotateVector((int*)&pPlayer->at1be.dx, (int*)&pPlayer->at1be.dy, pPSprite->ang);
+ pPlayer->at1be.dz += pPlayer->at83;
+ pPlayer->at1d6 = nTarget;
+}
+
+struct t_WeaponModes
+{
+ int at0;
+ int at4;
+};
+
+t_WeaponModes weaponModes[] = {
+ { 0, -1 },
+ { 1, -1 },
+ { 1, 1 },
+ { 1, 2 },
+ { 1, 3 },
+ { 1, 4 },
+ { 1, 5 },
+ { 1, 6 },
+ { 1, 7 },
+ { 1, 8 },
+ { 1, 9 },
+ { 1, 10 },
+ { 1, 11 },
+ { 0, -1 },
+};
+
+void WeaponRaise(PLAYER *pPlayer)
+{
+ dassert(pPlayer != NULL);
+ int prevWeapon = pPlayer->atbd;
+ pPlayer->atbd = pPlayer->atc.newWeapon;
+ pPlayer->atc.newWeapon = 0;
+ pPlayer->atc7 = weaponModes[pPlayer->atbd].at4;
+ switch (pPlayer->atbd)
+ {
+ case 1: // pitchfork
+ pPlayer->atc3 = 0;
+ StartQAV(pPlayer, 0, -1, 0);
+ break;
+ case 7: // spraycan
+ if (pPlayer->atc3 == 2)
+ {
+ pPlayer->atc3 = 3;
+ StartQAV(pPlayer, 8, -1, 0);
+ }
+ else
+ {
+ pPlayer->atc3 = 0;
+ StartQAV(pPlayer, 4, -1, 0);
+ }
+ break;
+ case 6: // dynamite
+ if (gInfiniteAmmo || sub_4B2C8(pPlayer, 5, 1))
+ {
+ pPlayer->atc3 = 3;
+ if (prevWeapon == 7)
+ StartQAV(pPlayer, 16, -1, 0);
+ else
+ StartQAV(pPlayer, 18, -1, 0);
+ }
+ break;
+ case 11: // proximity
+ if (gInfiniteAmmo || sub_4B2C8(pPlayer, 10, 1))
+ {
+ pPlayer->atc3 = 7;
+ StartQAV(pPlayer, 25, -1, 0);
+ }
+ break;
+ case 12: // remote
+ if (gInfiniteAmmo || sub_4B2C8(pPlayer, 11, 1))
+ {
+ pPlayer->atc3 = 10;
+ StartQAV(pPlayer, 31, -1, 0);
+ }
+ else
+ {
+ StartQAV(pPlayer, 32, -1, 0);
+ pPlayer->atc3 = 11;
+ }
+ break;
+ case 3: // sawed off
+ if (powerupCheck(pPlayer, 17))
+ {
+ if (gInfiniteAmmo || pPlayer->at181[2] >= 4)
+ StartQAV(pPlayer, 59, -1, 0);
+ else
+ StartQAV(pPlayer, 50, -1, 0);
+ if (gInfiniteAmmo || pPlayer->at181[2] >= 4)
+ pPlayer->atc3 = 7;
+ else if (pPlayer->at181[2] > 1)
+ pPlayer->atc3 = 3;
+ else if (pPlayer->at181[2] > 0)
+ pPlayer->atc3 = 2;
+ else
+ pPlayer->atc3 = 1;
+ }
+ else
+ {
+ if (gInfiniteAmmo || pPlayer->at181[2] > 1)
+ pPlayer->atc3 = 3;
+ else if (pPlayer->at181[2] > 0)
+ pPlayer->atc3 = 2;
+ else
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 50, -1, 0);
+ }
+ break;
+ case 4: // tommy gun
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 3, 2))
+ {
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 69, -1, 0);
+ }
+ else
+ {
+ pPlayer->atc3 = 0;
+ StartQAV(pPlayer, 64, -1, 0);
+ }
+ break;
+ case 10: // voodoo
+ if (gInfiniteAmmo || sub_4B2C8(pPlayer, 9, 1))
+ {
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 100, -1, 0);
+ }
+ break;
+ case 2: // flaregun
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 1, 2))
+ {
+ StartQAV(pPlayer, 45, -1, 0);
+ pPlayer->atc3 = 3;
+ }
+ else
+ {
+ StartQAV(pPlayer, 41, -1, 0);
+ pPlayer->atc3 = 2;
+ }
+ break;
+ case 8: // tesla cannon
+ if (sub_4B2C8(pPlayer, 7, 1))
+ {
+ pPlayer->atc3 = 2;
+ if (powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 82, -1, 0);
+ else
+ StartQAV(pPlayer, 74, -1, 0);
+ }
+ else
+ {
+ pPlayer->atc3 = 3;
+ StartQAV(pPlayer, 74, -1, 0);
+ }
+ break;
+ case 5: // napalm
+ if (powerupCheck(pPlayer, 17))
+ {
+ StartQAV(pPlayer, 120, -1, 0);
+ pPlayer->atc3 = 3;
+ }
+ else
+ {
+ StartQAV(pPlayer, 89, -1, 0);
+ pPlayer->atc3 = 2;
+ }
+ break;
+ case 9: // life leech
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 111, -1, 0);
+ break;
+ case 13: // beast
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 93, -1, 0);
+ break;
+ }
+}
+
+void WeaponLower(PLAYER *pPlayer)
+{
+ dassert(pPlayer != NULL);
+ if (sub_4B1A4(pPlayer))
+ return;
+ pPlayer->at1ba = 0;
+ int prevState = pPlayer->atc3;
+ switch (pPlayer->atbd)
+ {
+ case 1:
+ StartQAV(pPlayer, 3, -1, 0);
+ break;
+ case 7:
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ switch (prevState)
+ {
+ case 1:
+ StartQAV(pPlayer, 7, -1, 0);
+ break;
+ case 2:
+ pPlayer->atc3 = 1;
+ WeaponRaise(pPlayer);
+ return;
+ case 4:
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 11, -1, 0);
+ pPlayer->atc.newWeapon = 0;
+ WeaponLower(pPlayer);
+ break;
+ case 3:
+ if (pPlayer->atc.newWeapon == 6)
+ {
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 11, -1, 0);
+ return;
+ }
+ else if (pPlayer->atc.newWeapon == 7)
+ {
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 11, -1, 0);
+ pPlayer->atc.newWeapon = 0;
+ WeaponLower(pPlayer);
+ }
+ else
+ {
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 11, -1, 0);
+ }
+ break;
+ }
+ break;
+ case 6:
+ switch (prevState)
+ {
+ case 1:
+ StartQAV(pPlayer, 7, -1, 0);
+ break;
+ case 2:
+ WeaponRaise(pPlayer);
+ break;
+ case 3:
+ if (pPlayer->atc.newWeapon == 7)
+ {
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 17, -1, 0);
+ }
+ else
+ {
+ StartQAV(pPlayer, 19, -1, 0);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case 11:
+ switch (prevState)
+ {
+ case 7:
+ StartQAV(pPlayer, 26, -1, 0);
+ break;
+ }
+ break;
+ case 12:
+ switch (prevState)
+ {
+ case 10:
+ StartQAV(pPlayer, 34, -1, 0);
+ break;
+ case 11:
+ StartQAV(pPlayer, 35, -1, 0);
+ break;
+ }
+ break;
+ case 3:
+ if (powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 63, -1, 0);
+ else
+ StartQAV(pPlayer, 58, -1, 0);
+ break;
+ case 4:
+ if (powerupCheck(pPlayer, 17) && pPlayer->atc3 == 1)
+ StartQAV(pPlayer, 72, -1, 0);
+ else
+ StartQAV(pPlayer, 68, -1, 0);
+ break;
+ case 2:
+ if (powerupCheck(pPlayer, 17) && pPlayer->atc3 == 3)
+ StartQAV(pPlayer, 49, -1, 0);
+ else
+ StartQAV(pPlayer, 44, -1, 0);
+ break;
+ case 10:
+ StartQAV(pPlayer, 109, -1, 0);
+ break;
+ case 8:
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 88, -1, 0);
+ else
+ StartQAV(pPlayer, 81, -1, 0);
+ break;
+ case 5:
+ if (powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 124, -1, 0);
+ else
+ StartQAV(pPlayer, 92, -1, 0);
+ break;
+ case 9:
+ StartQAV(pPlayer, 119, -1, 0);
+ break;
+ case 13:
+ StartQAV(pPlayer, 99, -1, 0);
+ break;
+ }
+ pPlayer->atbd = 0;
+ pPlayer->at1b1 = 0;
+}
+
+void WeaponUpdateState(PLAYER *pPlayer)
+{
+ static int lastWeapon = 0;
+ static int lastState = 0;
+ XSPRITE *pXSprite = pPlayer->pXSprite;
+ int va = pPlayer->atbd;
+ int vb = pPlayer->atc3;
+ if (va != lastWeapon || vb != lastState)
+ {
+ lastWeapon = va;
+ lastState = vb;
+ }
+ switch (lastWeapon)
+ {
+ case 1:
+ pPlayer->at26 = 1;
+ break;
+ case 7:
+ switch (vb)
+ {
+ case 0:
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 5, -1, 0);
+ break;
+ case 1:
+ if (CheckAmmo(pPlayer, 6, 1))
+ {
+ pPlayer->atc3 = 3;
+ StartQAV(pPlayer, 8, -1, 0);
+ }
+ else
+ pPlayer->at26 = 6;
+ break;
+ case 3:
+ pPlayer->at26 = 9;
+ break;
+ case 4:
+ if (CheckAmmo(pPlayer, 6, 1))
+ {
+ pPlayer->at26 = 9;
+ pPlayer->atc3 = 3;
+ }
+ else
+ {
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 11, -1, 0);
+ }
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ break;
+ }
+ break;
+ case 6:
+ switch (vb)
+ {
+ case 1:
+ if (pPlayer->atc7 == 5 && CheckAmmo(pPlayer, 5, 1))
+ {
+ pPlayer->atc3 = 3;
+ StartQAV(pPlayer, 16, -1, 0);
+ }
+ break;
+ case 0:
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 5, -1, 0);
+ break;
+ case 2:
+ if (pPlayer->at181[5] > 0)
+ {
+ pPlayer->atc3 = 3;
+ StartQAV(pPlayer, 16, -1, 0);
+ }
+ else
+ pPlayer->at26 = 6;
+ break;
+ case 3:
+ pPlayer->at26 = 20;
+ break;
+ }
+ break;
+ case 11:
+ switch (vb)
+ {
+ case 7:
+ pPlayer->at26 = 27;
+ break;
+ case 8:
+ pPlayer->atc3 = 7;
+ StartQAV(pPlayer, 25, -1, 0);
+ break;
+ }
+ break;
+ case 12:
+ switch (vb)
+ {
+ case 10:
+ pPlayer->at26 = 36;
+ break;
+ case 11:
+ pPlayer->at26 = 37;
+ break;
+ case 12:
+ if (pPlayer->at181[11] > 0)
+ {
+ pPlayer->atc3 = 10;
+ StartQAV(pPlayer, 31, -1, 0);
+ }
+ else
+ pPlayer->atc3 = -1;
+ break;
+ }
+ break;
+ case 3:
+ switch (vb)
+ {
+ case 6:
+ if (powerupCheck(pPlayer, 17) && (gInfiniteAmmo || CheckAmmo(pPlayer, 2, 4)))
+ pPlayer->atc3 = 7;
+ else
+ pPlayer->atc3 = 1;
+ break;
+ case 7:
+ pPlayer->at26 = 60;
+ break;
+ case 1:
+ if (CheckAmmo(pPlayer, 2, 1))
+ {
+ sfxPlay3DSound(pPlayer->pSprite, 410, 3, 2);
+ StartQAV(pPlayer, 57, nClientEjectShell, 0);
+ if (gInfiniteAmmo || pPlayer->at181[2] > 1)
+ pPlayer->atc3 = 3;
+ else
+ pPlayer->atc3 = 2;
+ }
+ else
+ pPlayer->at26 = 51;
+ break;
+ case 2:
+ pPlayer->at26 = 52;
+ break;
+ case 3:
+ pPlayer->at26 = 53;
+ break;
+ }
+ break;
+ case 4:
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 3, 2))
+ {
+ pPlayer->at26 = 70;
+ pPlayer->atc3 = 1;
+ }
+ else
+ {
+ pPlayer->at26 = 65;
+ pPlayer->atc3 = 0;
+ }
+ break;
+ case 2:
+ if (powerupCheck(pPlayer, 17))
+ {
+ if (vb == 3 && sub_4B2C8(pPlayer, 1, 2))
+ pPlayer->at26 = 46;
+ else
+ {
+ pPlayer->at26 = 42;
+ pPlayer->atc3 = 2;
+ }
+ }
+ else
+ pPlayer->at26 = 42;
+ break;
+ case 10:
+ if (pXSprite->height < 256 && klabs(pPlayer->at4f) > 768)
+ pPlayer->at26 = 102;
+ else
+ pPlayer->at26 = 101;
+ break;
+ case 8:
+ switch (vb)
+ {
+ case 2:
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ pPlayer->at26 = 83;
+ else
+ pPlayer->at26 = 75;
+ break;
+ case 3:
+ pPlayer->at26 = 76;
+ break;
+ }
+ break;
+ case 5:
+ switch (vb)
+ {
+ case 3:
+ if (powerupCheck(pPlayer, 17) && (gInfiniteAmmo || CheckAmmo(pPlayer,4, 4)))
+ pPlayer->at26 = 121;
+ else
+ pPlayer->at26 = 90;
+ break;
+ case 2:
+ pPlayer->at26 = 90;
+ break;
+ }
+ break;
+ case 9:
+ switch (vb)
+ {
+ case 2:
+ pPlayer->at26 = 112;
+ break;
+ }
+ break;
+ case 13:
+ pPlayer->at26 = 94;
+ break;
+ }
+}
+
+void FirePitchfork(int, PLAYER *pPlayer)
+{
+ Aim *aim = &pPlayer->at1be;
+ int r1 = Random2(2000);
+ int r2 = Random2(2000);
+ int r3 = Random2(2000);
+ for (int i = 0; i < 4; i++)
+ actFireVector(pPlayer->pSprite, (2*i-3)*40, pPlayer->at6f-pPlayer->pSprite->z, aim->dx+r1, aim->dy+r2, aim->dz+r3, VECTOR_TYPE_0);
+}
+
+void FireSpray(int, PLAYER *pPlayer)
+{
+ playerFireMissile(pPlayer, 0, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 304);
+ UseAmmo(pPlayer, 6, 4);
+ if (CheckAmmo(pPlayer, 6, 1))
+ sfxPlay3DSound(pPlayer->pSprite, 441, 1, 2);
+ else
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+}
+
+void ThrowCan(int, PLAYER *pPlayer)
+{
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ int nSpeed = mulscale16(pPlayer->at1ba, 0x177777)+0x66666;
+ sfxPlay3DSound(pPlayer->pSprite, 455, 1, 0);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, -9460, 420, nSpeed);
+ if (pSprite)
+ {
+ sfxPlay3DSound(pSprite, 441, 0, 0);
+ evPost(pSprite->index, 3, pPlayer->at1b2, COMMAND_ID_1);
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ pXSprite->Impact = 1;
+ UseAmmo(pPlayer, 6, gAmmoItemData[0].at8);
+ pPlayer->at1ba = 0;
+ }
+}
+
+void DropCan(int, PLAYER *pPlayer)
+{
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, 0, 420, 0);
+ if (pSprite)
+ {
+ evPost(pSprite->index, 3, pPlayer->at1b2, COMMAND_ID_1);
+ UseAmmo(pPlayer, 6, gAmmoItemData[0].at8);
+ }
+}
+
+void ExplodeCan(int, PLAYER *pPlayer)
+{
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, 0, 420, 0);
+ evPost(pSprite->index, 3, 0, COMMAND_ID_1);
+ UseAmmo(pPlayer, 6, gAmmoItemData[0].at8);
+ StartQAV(pPlayer, 15, -1);
+ pPlayer->atbd = 0;
+ pPlayer->at1ba = 0;
+}
+
+void ThrowBundle(int, PLAYER *pPlayer)
+{
+ sfxKill3DSound(pPlayer->pSprite, 16, -1);
+ int nSpeed = mulscale16(pPlayer->at1ba, 0x177777)+0x66666;
+ sfxPlay3DSound(pPlayer->pSprite, 455, 1, 0);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, -9460, 419, nSpeed);
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ if (pPlayer->at1b2 < 0)
+ pXSprite->Impact = 1;
+ else
+ evPost(pSprite->index, 3, pPlayer->at1b2, COMMAND_ID_1);
+ UseAmmo(pPlayer, 5, 1);
+ pPlayer->at1ba = 0;
+}
+
+void DropBundle(int, PLAYER *pPlayer)
+{
+ sfxKill3DSound(pPlayer->pSprite, 16, -1);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, 0, 419, 0);
+ evPost(pSprite->index, 3, pPlayer->at1b2, COMMAND_ID_1);
+ UseAmmo(pPlayer, 5, 1);
+}
+
+void ExplodeBundle(int, PLAYER *pPlayer)
+{
+ sfxKill3DSound(pPlayer->pSprite, 16, -1);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, 0, 419, 0);
+ evPost(pSprite->index, 3, 0, COMMAND_ID_1);
+ UseAmmo(pPlayer, 5, 1);
+ StartQAV(pPlayer, 24, -1, 0);
+ pPlayer->atbd = 0;
+ pPlayer->at1ba = 0;
+}
+
+void ThrowProx(int, PLAYER *pPlayer)
+{
+ int nSpeed = mulscale16(pPlayer->at1ba, 0x177777)+0x66666;
+ sfxPlay3DSound(pPlayer->pSprite, 455, 1, 0);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, -9460, 401, nSpeed);
+ evPost(pSprite->index, 3, 240, COMMAND_ID_1);
+ UseAmmo(pPlayer, 10, 1);
+ pPlayer->at1ba = 0;
+}
+
+void DropProx(int, PLAYER *pPlayer)
+{
+ spritetype *pSprite = playerFireThing(pPlayer, 0, 0, 401, 0);
+ evPost(pSprite->index, 3, 240, COMMAND_ID_1);
+ UseAmmo(pPlayer, 10, 1);
+}
+
+void ThrowRemote(int, PLAYER *pPlayer)
+{
+ int nSpeed = mulscale16(pPlayer->at1ba, 0x177777)+0x66666;
+ sfxPlay3DSound(pPlayer->pSprite, 455, 1, 0);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, -9460, 402, nSpeed);
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ pXSprite->rxID = 90+(pPlayer->pSprite->type-kDudePlayer1);
+ UseAmmo(pPlayer, 11, 1);
+ pPlayer->at1ba = 0;
+}
+
+void DropRemote(int, PLAYER *pPlayer)
+{
+ spritetype *pSprite = playerFireThing(pPlayer, 0, 0, 402, 0);
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ pXSprite->rxID = 90+(pPlayer->pSprite->type-kDudePlayer1);
+ UseAmmo(pPlayer, 11, 1);
+}
+
+void FireRemote(int, PLAYER *pPlayer)
+{
+ evSend(0, 0, 90+(pPlayer->pSprite->type-kDudePlayer1), COMMAND_ID_1);
+}
+
+#define kMaxShotgunBarrels 4
+
+void FireShotgun(int nTrigger, PLAYER *pPlayer)
+{
+ dassert(nTrigger > 0 && nTrigger <= kMaxShotgunBarrels);
+ if (nTrigger == 1)
+ {
+ sfxPlay3DSound(pPlayer->pSprite, 411, 2, 0);
+ pPlayer->at35e = 30;
+ pPlayer->at362 = 20;
+ }
+ else
+ {
+ sfxPlay3DSound(pPlayer->pSprite, 412, 2, 0);
+ pPlayer->at35e = 50;
+ pPlayer->at362 = 40;
+ }
+ int n = nTrigger<<4;
+ for (int i = 0; i < n; i++)
+ {
+ int r1, r2, r3;
+ VECTOR_TYPE nType;
+ if (nTrigger == 1)
+ {
+ r1 = Random3(1500);
+ r2 = Random3(1500);
+ r3 = Random3(500);
+ nType = VECTOR_TYPE_1;
+ }
+ else
+ {
+ r1 = Random3(2500);
+ r2 = Random3(2500);
+ r3 = Random3(1500);
+ nType = VECTOR_TYPE_4;
+ }
+ actFireVector(pPlayer->pSprite, 0, pPlayer->at6f-pPlayer->pSprite->z, pPlayer->at1be.dx+r1, pPlayer->at1be.dy+r2, pPlayer->at1be.dz+r3, nType);
+ }
+ UseAmmo(pPlayer, pPlayer->atc7, nTrigger);
+ pPlayer->at37b = 1;
+}
+
+void EjectShell(int, PLAYER *pPlayer)
+{
+ SpawnShellEject(pPlayer, 25, 35);
+ SpawnShellEject(pPlayer, 48, 35);
+}
+
+void FireTommy(int nTrigger, PLAYER *pPlayer)
+{
+ Aim *aim = &pPlayer->at1be;
+ sfxPlay3DSound(pPlayer->pSprite, 431, -1, 0);
+ switch (nTrigger)
+ {
+ case 1:
+ {
+ int r1 = Random3(400);
+ int r2 = Random3(1200);
+ int r3 = Random3(1200);
+ actFireVector(pPlayer->pSprite, 0, pPlayer->at6f-pPlayer->pSprite->z, aim->dx+r3, aim->dy+r2, aim->dz+r1, VECTOR_TYPE_5);
+ SpawnBulletEject(pPlayer, -15, -45);
+ pPlayer->at362 = 20;
+ break;
+ }
+ case 2:
+ {
+ int r1 = Random3(400);
+ int r2 = Random3(1200);
+ int r3 = Random3(1200);
+ actFireVector(pPlayer->pSprite, -120, pPlayer->at6f-pPlayer->pSprite->z, aim->dx+r3, aim->dy+r2, aim->dz+r1, VECTOR_TYPE_5);
+ SpawnBulletEject(pPlayer, -140, -45);
+ r1 = Random3(400);
+ r2 = Random3(1200);
+ r3 = Random3(1200);
+ actFireVector(pPlayer->pSprite, 120, pPlayer->at6f-pPlayer->pSprite->z, aim->dx+r3, aim->dy+r2, aim->dz+r1, VECTOR_TYPE_5);
+ SpawnBulletEject(pPlayer, 140, 45);
+ pPlayer->at362 = 30;
+ break;
+ }
+ }
+ UseAmmo(pPlayer, pPlayer->atc7, nTrigger);
+ pPlayer->at37b = 1;
+}
+
+#define kMaxSpread 14
+
+void FireSpread(int nTrigger, PLAYER *pPlayer)
+{
+ dassert(nTrigger > 0 && nTrigger <= kMaxSpread);
+ Aim *aim = &pPlayer->at1be;
+ int angle = (getangle(aim->dx, aim->dy)+((112*(nTrigger-1))/14-56))&2047;
+ int dx = Cos(angle)>>16;
+ int dy = Sin(angle)>>16;
+ sfxPlay3DSound(pPlayer->pSprite, 431, -1, 0);
+ int r1, r2, r3;
+ r1 = Random3(300);
+ r2 = Random3(600);
+ r3 = Random3(600);
+ actFireVector(pPlayer->pSprite, 0, pPlayer->at6f-pPlayer->pSprite->z, dx+r3, dy+r2, aim->dz+r1, VECTOR_TYPE_3);
+ r1 = Random2(90);
+ r2 = Random2(30);
+ SpawnBulletEject(pPlayer, r2, r1);
+ pPlayer->at362 = 20;
+ UseAmmo(pPlayer, pPlayer->atc7, 1);
+ pPlayer->at37b = 1;
+}
+
+void AltFireSpread(int nTrigger, PLAYER *pPlayer)
+{
+ dassert(nTrigger > 0 && nTrigger <= kMaxSpread);
+ Aim *aim = &pPlayer->at1be;
+ int angle = (getangle(aim->dx, aim->dy)+((112*(nTrigger-1))/14-56))&2047;
+ int dx = Cos(angle)>>16;
+ int dy = Sin(angle)>>16;
+ sfxPlay3DSound(pPlayer->pSprite, 431, -1, 0);
+ int r1, r2, r3;
+ r1 = Random3(300);
+ r2 = Random3(600);
+ r3 = Random3(600);
+ actFireVector(pPlayer->pSprite, -120, pPlayer->at6f-pPlayer->pSprite->z, dx+r3, dy+r2, aim->dz+r1, VECTOR_TYPE_3);
+ r1 = Random2(45);
+ r2 = Random2(120);
+ SpawnBulletEject(pPlayer, r2, r1);
+ r1 = Random3(300);
+ r2 = Random3(600);
+ r3 = Random3(600);
+ actFireVector(pPlayer->pSprite, 120, pPlayer->at6f-pPlayer->pSprite->z, dx+r3, dy+r2, aim->dz+r1, VECTOR_TYPE_3);
+ r1 = Random2(-45);
+ r2 = Random2(-120);
+ SpawnBulletEject(pPlayer, r2, r1);
+ pPlayer->at35e = 20;
+ pPlayer->at362 = 30;
+ UseAmmo(pPlayer, pPlayer->atc7, 2);
+ pPlayer->at37b = 1;
+}
+
+void AltFireSpread2(int nTrigger, PLAYER *pPlayer)
+{
+ dassert(nTrigger > 0 && nTrigger <= kMaxSpread);
+ Aim *aim = &pPlayer->at1be;
+ int angle = (getangle(aim->dx, aim->dy)+((112*(nTrigger-1))/14-56))&2047;
+ int dx = Cos(angle)>>16;
+ int dy = Sin(angle)>>16;
+ sfxPlay3DSound(pPlayer->pSprite, 431, -1, 0);
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 3, 2))
+ {
+ int r1, r2, r3;
+ r1 = Random3(300);
+ r2 = Random3(600);
+ r3 = Random3(600);
+ actFireVector(pPlayer->pSprite, -120, pPlayer->at6f-pPlayer->pSprite->z, dx+r3, dy+r2, aim->dz+r1, VECTOR_TYPE_3);
+ r1 = Random2(45);
+ r2 = Random2(120);
+ SpawnBulletEject(pPlayer, r2, r1);
+ r1 = Random3(300);
+ r2 = Random3(600);
+ r3 = Random3(600);
+ actFireVector(pPlayer->pSprite, 120, pPlayer->at6f-pPlayer->pSprite->z, dx+r3, dy+r2, aim->dz+r1, VECTOR_TYPE_3);
+ r1 = Random2(-45);
+ r2 = Random2(-120);
+ SpawnBulletEject(pPlayer, r2, r1);
+ pPlayer->at35e = 30;
+ pPlayer->at362 = 45;
+ UseAmmo(pPlayer, pPlayer->atc7, 2);
+ }
+ else
+ {
+ int r1, r2, r3;
+ r1 = Random3(300);
+ r2 = Random3(600);
+ r3 = Random3(600);
+ actFireVector(pPlayer->pSprite, 0, pPlayer->at6f-pPlayer->pSprite->z, dx+r3, dy+r2, aim->dz+r1, VECTOR_TYPE_3);
+ r1 = Random2(90);
+ r2 = Random2(30);
+ SpawnBulletEject(pPlayer, r2, r1);
+ pPlayer->at35e = 20;
+ pPlayer->at362 = 30;
+ UseAmmo(pPlayer, pPlayer->atc7, 1);
+ }
+ pPlayer->at37b = 1;
+ if (!sub_4B2C8(pPlayer, 3, 1))
+ {
+ WeaponLower(pPlayer);
+ pPlayer->atc3 = -1;
+ }
+}
+
+void FireFlare(int nTrigger, PLAYER *pPlayer)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ int offset = 0;
+ switch (nTrigger)
+ {
+ case 2:
+ offset = -120;
+ break;
+ case 3:
+ offset = 120;
+ break;
+ }
+ playerFireMissile(pPlayer, offset, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 301);
+ UseAmmo(pPlayer, 1, 1);
+ sfxPlay3DSound(pSprite, 420, 2, 0);
+ pPlayer->at362 = 30;
+ pPlayer->at37b = 1;
+}
+
+void AltFireFlare(int nTrigger, PLAYER *pPlayer)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ int offset = 0;
+ switch (nTrigger)
+ {
+ case 2:
+ offset = -120;
+ break;
+ case 3:
+ offset = 120;
+ break;
+ }
+ playerFireMissile(pPlayer, offset, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 303);
+ UseAmmo(pPlayer, 1, 8);
+ sfxPlay3DSound(pSprite, 420, 2, 0);
+ pPlayer->at362 = 45;
+ pPlayer->at37b = 1;
+}
+
+void FireVoodoo(int nTrigger, PLAYER *pPlayer)
+{
+ nTrigger--;
+ int nSprite = pPlayer->at5b;
+ spritetype *pSprite = pPlayer->pSprite;
+ if (nTrigger == 4)
+ {
+ actDamageSprite(nSprite, pSprite, DAMAGE_TYPE_2, 1<<4);
+ return;
+ }
+ dassert(pPlayer->voodooTarget >= 0);
+ spritetype *pTarget = &sprite[pPlayer->voodooTarget];
+ switch (nTrigger)
+ {
+ case 0:
+ {
+ sfxPlay3DSound(pSprite, 460, 2, 0);
+ fxSpawnBlood(pTarget, 17<<4);
+ int nDamage = actDamageSprite(nSprite, pTarget, DAMAGE_TYPE_5, 17<<4);
+ UseAmmo(pPlayer, 9, nDamage/4);
+ break;
+ }
+ case 1:
+ {
+ sfxPlay3DSound(pSprite, 460, 2, 0);
+ fxSpawnBlood(pTarget, 17<<4);
+ int nDamage = actDamageSprite(nSprite, pTarget, DAMAGE_TYPE_5, 9<<4);
+ if (pTarget->type >= kDudePlayer1 && pTarget->type <= kDudePlayer8)
+ WeaponLower(&gPlayer[pTarget->type-kDudePlayer1]);
+ UseAmmo(pPlayer, 9, nDamage/4);
+ break;
+ }
+ case 3:
+ {
+ sfxPlay3DSound(pSprite, 463, 2, 0);
+ fxSpawnBlood(pTarget, 17<<4);
+ int nDamage = actDamageSprite(nSprite, pTarget, DAMAGE_TYPE_5, 49<<4);
+ UseAmmo(pPlayer, 9, nDamage/4);
+ break;
+ }
+ case 2:
+ {
+ sfxPlay3DSound(pSprite, 460, 2, 0);
+ fxSpawnBlood(pTarget, 17<<4);
+ int nDamage = actDamageSprite(nSprite, pTarget, DAMAGE_TYPE_5, 11<<4);
+ if (pTarget->type >= kDudePlayer1 && pTarget->type <= kDudePlayer8)
+ {
+ PLAYER *pOtherPlayer = &gPlayer[pTarget->type-kDudePlayer1];
+ pOtherPlayer->at36a = 128;
+ }
+ UseAmmo(pPlayer, 9, nDamage/4);
+ break;
+ }
+ }
+}
+
+void AltFireVoodoo(int nTrigger, PLAYER *pPlayer)
+{
+ if (nTrigger != 2)
+ return;
+ //int nAmmo = pPlayer->at181[9];
+ int nCount = ClipHigh(pPlayer->at181[9], pPlayer->at1da);
+ if (nCount > 0)
+ {
+ int v4 = pPlayer->at181[9] - (pPlayer->at181[9] / nCount)*nCount;
+ for (int i = 0; i < pPlayer->at1da; i++)
+ {
+ int nTarget = pPlayer->at1de[i];
+ spritetype *pTarget = &sprite[nTarget];
+ if (v4 > 0)
+ v4--;
+ int nDist = approxDist(pTarget->x-pPlayer->pSprite->x, pTarget->y-pPlayer->pSprite->y);
+ if (nDist > 0 && nDist < 51200)
+ {
+ int vc = pPlayer->at181[9]>>3;
+ int v8 = pPlayer->at181[9]<<1;
+ int nDamage = (v8+Random2(vc))<<4;
+ nDamage = (nDamage*((51200-nDist)+1))/51200;
+ nDamage = actDamageSprite(pPlayer->at5b, pTarget, DAMAGE_TYPE_5, nDamage);
+ UseAmmo(pPlayer, 9, nDamage);
+ if (pTarget->type >= kDudePlayer1 && pTarget->type <= kDudePlayer8)
+ {
+ PLAYER *pOtherPlayer = &gPlayer[pTarget->type-kDudePlayer1];
+ if (!pOtherPlayer->at31a || !powerupCheck(pOtherPlayer,14))
+ powerupActivate(pOtherPlayer, 28);
+ }
+ fxSpawnBlood(pTarget, 0);
+ }
+ }
+ }
+ UseAmmo(pPlayer, 9, pPlayer->at181[9]);
+ pPlayer->atcb[10] = 0;
+ pPlayer->atc3 = -1;
+}
+
+void DropVoodoo(int nTrigger, PLAYER *pPlayer)
+{
+ UNREFERENCED_PARAMETER(nTrigger);
+ sfxPlay3DSound(pPlayer->pSprite, 455, 2, 0);
+ spritetype *pSprite = playerFireThing(pPlayer, 0, -4730, 432, 0xccccc);
+ if (pSprite)
+ {
+ int nXSprite = pSprite->extra;
+ XSPRITE *pXSprite = &xsprite[nXSprite];
+ pXSprite->data1 = pPlayer->at181[9];
+ evPost(pSprite->index, 3, 90, CALLBACK_ID_21);
+ UseAmmo(pPlayer, 6, gAmmoItemData[0].at8);
+ UseAmmo(pPlayer, 9, pPlayer->at181[9]);
+ pPlayer->atcb[10] = 0;
+ }
+}
+
+struct TeslaMissile
+{
+ int at0; // offset
+ int at4; // id
+ int at8; // ammo use
+ int atc; // sound
+ int at10; // light
+ int at14; // weapon flash
+};
+
+void FireTesla(int nTrigger, PLAYER *pPlayer)
+{
+ TeslaMissile teslaMissile[6] =
+ {
+ { 0, 306, 1, 470, 20, 1 },
+ { -140, 306, 1, 470, 30, 1 },
+ { 140, 306, 1, 470, 30, 1 },
+ { 0, 302, 35, 471, 40, 1 },
+ { -140, 302, 35, 471, 50, 1 },
+ { 140, 302, 35, 471, 50, 1 },
+ };
+ if (nTrigger > 0 && nTrigger <= 6)
+ {
+ nTrigger--;
+ spritetype *pSprite = pPlayer->pSprite;
+ TeslaMissile *pMissile = &teslaMissile[nTrigger];
+ if (!sub_4B2C8(pPlayer, 7, pMissile->at8))
+ {
+ pMissile = &teslaMissile[0];
+ if (!sub_4B2C8(pPlayer, 7, pMissile->at8))
+ {
+ pPlayer->atc3 = -1;
+ pPlayer->at26 = 76;
+ pPlayer->at37b = 0;
+ return;
+ }
+ }
+ playerFireMissile(pPlayer, pMissile->at0, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, pMissile->at4);
+ UseAmmo(pPlayer, 7, pMissile->at8);
+ sfxPlay3DSound(pSprite, pMissile->atc, 1, 0);
+ pPlayer->at362 = pMissile->at10;
+ pPlayer->at37b = pMissile->at14;
+ }
+}
+
+void AltFireTesla(int nTrigger, PLAYER *pPlayer)
+{
+ UNREFERENCED_PARAMETER(nTrigger);
+ spritetype *pSprite = pPlayer->pSprite;
+ playerFireMissile(pPlayer, 0, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 302);
+ UseAmmo(pPlayer, pPlayer->atc7, 35);
+ sfxPlay3DSound(pSprite, 471, 2, 0);
+ pPlayer->at362 = 40;
+ pPlayer->at37b = 1;
+}
+
+void FireNapalm(int nTrigger, PLAYER *pPlayer)
+{
+ spritetype *pSprite = pPlayer->pSprite;
+ int offset = 0;
+ switch (nTrigger)
+ {
+ case 2:
+ offset = -50;
+ break;
+ case 3:
+ offset = 50;
+ break;
+ }
+ playerFireMissile(pPlayer, offset, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 312);
+ sfxPlay3DSound(pSprite, 480, 2, 0);
+ UseAmmo(pPlayer, 4, 1);
+ pPlayer->at37b = 1;
+}
+
+void FireNapalm2(int nTrigger, PLAYER *pPlayer)
+{
+ UNREFERENCED_PARAMETER(nTrigger);
+ spritetype *pSprite = pPlayer->pSprite;
+ playerFireMissile(pPlayer, -120, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 312);
+ playerFireMissile(pPlayer, 120, pPlayer->at1be.dx, pPlayer->at1be.dy, pPlayer->at1be.dz, 312);
+ sfxPlay3DSound(pSprite, 480, 2, 0);
+ UseAmmo(pPlayer, 4, 2);
+ pPlayer->at37b = 1;
+}
+
+void AltFireNapalm(int nTrigger, PLAYER *pPlayer)
+{
+ UNREFERENCED_PARAMETER(nTrigger);
+ char UNUSED(bAkimbo) = powerupCheck(pPlayer, 17);
+ int nSpeed = mulscale16(0x8000, 0x177777)+0x66666;
+ spritetype *pMissile = playerFireThing(pPlayer, 0, -4730, 428, nSpeed);
+ if (pMissile)
+ {
+ XSPRITE *pXSprite = &xsprite[pMissile->extra];
+ pXSprite->data4 = ClipHigh(pPlayer->at181[4], 12);
+ UseAmmo(pPlayer, 4, pXSprite->data4);
+ seqSpawn(22, 3, pMissile->extra, -1);
+ actBurnSprite(actSpriteIdToOwnerId(pPlayer->pSprite->index), pXSprite, 600);
+ evPost(pMissile->index, 3, 0, CALLBACK_ID_0);
+ sfxPlay3DSound(pMissile, 480, 2, 0);
+ pPlayer->at362 = 30;
+ pPlayer->at37b = 1;
+ }
+}
+
+void FireLifeLeech(int nTrigger, PLAYER *pPlayer)
+{
+ if (!CheckAmmo(pPlayer, 8, 1))
+ return;
+ int r1 = Random2(2000);
+ int r2 = Random2(2000);
+ int r3 = Random2(1000);
+ spritetype *pMissile = playerFireMissile(pPlayer, 0, pPlayer->at1be.dx+r1, pPlayer->at1be.dy+r2, pPlayer->at1be.dz+r3, 315);
+ if (pMissile)
+ {
+ XSPRITE *pXSprite = &xsprite[pMissile->extra];
+ pXSprite->target = pPlayer->at1d6;
+ pMissile->ang = (nTrigger==2) ? 1024 : 0;
+ }
+ if (sub_4B2C8(pPlayer, 8, 1))
+ UseAmmo(pPlayer, 8, 1);
+ else
+ actDamageSprite(pPlayer->at5b, pPlayer->pSprite, DAMAGE_TYPE_5, 16);
+ pPlayer->at362 = ClipHigh(pPlayer->at362+5, 50);
+}
+
+void AltFireLifeLeech(int nTrigger, PLAYER *pPlayer)
+{
+ UNREFERENCED_PARAMETER(nTrigger);
+ sfxPlay3DSound(pPlayer->pSprite, 455, 2, 0);
+ spritetype *pMissile = playerFireThing(pPlayer, 0, -4730, 431, 0x19999);
+ if (pMissile)
+ {
+ pMissile->cstat |= 4096;
+ XSPRITE *pXSprite = &xsprite[pMissile->extra];
+ pXSprite->Push = 1;
+ pXSprite->Proximity = 1;
+ pXSprite->DudeLockout = 1;
+ pXSprite->data4 = ClipHigh(pPlayer->at181[4], 12);
+ pXSprite->stateTimer = 1;
+ evPost(pMissile->index, 3, 120, CALLBACK_ID_20);
+ if (gGameOptions.nGameType <= 1)
+ {
+ int nAmmo = pPlayer->at181[8];
+ if (nAmmo < 25 && pPlayer->pXSprite->health > ((25-nAmmo)<<4))
+ {
+ actDamageSprite(pPlayer->at5b, pPlayer->pSprite, DAMAGE_TYPE_5, ((25-nAmmo)<<4));
+ nAmmo = 25;
+ }
+ pXSprite->data3 = nAmmo;
+ UseAmmo(pPlayer, 8, nAmmo);
+ }
+ else
+ {
+ pXSprite->data3 = pPlayer->at181[8];
+ pPlayer->at181[8] = 0;
+ }
+ pPlayer->atcb[9] = 0;
+ }
+}
+
+void FireBeast(int nTrigger, PLAYER * pPlayer)
+{
+ UNREFERENCED_PARAMETER(nTrigger);
+ int r1 = Random2(2000);
+ int r2 = Random2(2000);
+ int r3 = Random2(2000);
+ actFireVector(pPlayer->pSprite, 0, pPlayer->at6f-pPlayer->pSprite->z, pPlayer->at1be.dx+r1, pPlayer->at1be.dy+r2, pPlayer->at1be.dz+r3, VECTOR_TYPE_9);
+}
+
+char gWeaponUpgrade[][13] = {
+ { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+ { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+ { 0, 0, 1, 1, 1, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0 },
+ { 0, 0, 1, 1, 1, 0, 0, 1, 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, 0, 0, 0 },
+ { 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 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, 1 },
+};
+
+char WeaponUpgrade(PLAYER *pPlayer, char newWeapon)
+{
+ char weapon = pPlayer->atbd;
+ if (!sub_4B1A4(pPlayer) && (gProfile[pPlayer->at57].nWeaponSwitch&1) && (gWeaponUpgrade[pPlayer->atbd][newWeapon] || (gProfile[pPlayer->at57].nWeaponSwitch&2)))
+ weapon = newWeapon;
+ return weapon;
+}
+
+int OrderNext[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 1 };
+int OrderPrev[] = { 12, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1 };
+
+char WeaponFindNext(PLAYER *pPlayer, int *a2, char bDir)
+{
+ int weapon = pPlayer->atbd;
+ do
+ {
+ if (bDir)
+ weapon = OrderNext[weapon];
+ else
+ weapon = OrderPrev[weapon];
+ if (weaponModes[weapon].at0 && pPlayer->atcb[weapon])
+ {
+ if (weapon == 9)
+ {
+ if (CheckAmmo(pPlayer, weaponModes[weapon].at4, 1))
+ break;
+ }
+ else
+ {
+ if (sub_4B2C8(pPlayer, weaponModes[weapon].at4, 1))
+ break;
+ }
+ }
+ } while (weapon != pPlayer->atbd);
+ if (weapon == pPlayer->atbd)
+ {
+ if (!weaponModes[weapon].at0 || !CheckAmmo(pPlayer, weaponModes[weapon].at4, 1))
+ weapon = 1;
+ }
+ if (a2)
+ *a2 = 0;
+ return weapon;
+}
+
+char WeaponFindLoaded(PLAYER *pPlayer, int *a2)
+{
+ char v4 = 1;
+ int v14 = 0;
+ if (weaponModes[pPlayer->atbd].at0 > 1)
+ {
+ for (int i = 0; i < weaponModes[pPlayer->atbd].at0; i++)
+ {
+ if (CheckAmmo(pPlayer, weaponModes[pPlayer->atbd].at4, 1))
+ {
+ v14 = i;
+ v4 = pPlayer->atbd;
+ break;
+ }
+ }
+ }
+ if (v4 == 1)
+ {
+ int vc = 0;
+ for (int i = 0; i < 14; i++)
+ {
+ int weapon = pPlayer->at111[vc][i];
+ if (pPlayer->atcb[weapon])
+ {
+ for (int j = 0; j < weaponModes[weapon].at0; j++)
+ {
+ if (sub_4B1FC(pPlayer, weapon, weaponModes[weapon].at4, 1))
+ {
+ if (a2)
+ *a2 = j;
+ return weapon;
+ }
+ }
+ }
+ }
+ }
+ else if (a2)
+ *a2 = v14;
+ return v4;
+}
+
+char sub_4F0E0(PLAYER *pPlayer)
+{
+ switch (pPlayer->atc3)
+ {
+ case 5:
+ if (!pPlayer->atc.buttonFlags.shoot2)
+ pPlayer->atc3 = 6;
+ return 1;
+ case 6:
+ if (pPlayer->atc.buttonFlags.shoot2)
+ {
+ pPlayer->atc3 = 3;
+ pPlayer->at1b2 = pPlayer->atbf;
+ StartQAV(pPlayer, 13, nClientDropCan, 0);
+ }
+ else if (pPlayer->atc.buttonFlags.shoot)
+ {
+ pPlayer->atc3 = 7;
+ pPlayer->at1b2 = 0;
+ pPlayer->at1b6 = gFrameClock;
+ }
+ return 1;
+ case 7:
+ {
+ pPlayer->at1ba = ClipHigh(divscale16(gFrameClock-pPlayer->at1b6,240), 65536);
+ if (!pPlayer->atc.buttonFlags.shoot)
+ {
+ if (!pPlayer->at1b2)
+ pPlayer->at1b2 = pPlayer->atbf;
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 14, nClientThrowCan, 0);
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+char sub_4F200(PLAYER *pPlayer)
+{
+ switch (pPlayer->atc3)
+ {
+ case 4:
+ if (!pPlayer->atc.buttonFlags.shoot2)
+ pPlayer->atc3 = 5;
+ return 1;
+ case 5:
+ if (pPlayer->atc.buttonFlags.shoot2)
+ {
+ pPlayer->atc3 = 1;
+ pPlayer->at1b2 = pPlayer->atbf;
+ StartQAV(pPlayer, 22, nClientDropBundle, 0);
+ }
+ else if (pPlayer->atc.buttonFlags.shoot)
+ {
+ pPlayer->atc3 = 6;
+ pPlayer->at1b2 = 0;
+ pPlayer->at1b6 = gFrameClock;
+ }
+ return 1;
+ case 6:
+ {
+ pPlayer->at1ba = ClipHigh(divscale16(gFrameClock-pPlayer->at1b6,240), 65536);
+ if (!pPlayer->atc.buttonFlags.shoot)
+ {
+ if (!pPlayer->at1b2)
+ pPlayer->at1b2 = pPlayer->atbf;
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 23, nClientThrowBundle, 0);
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+char sub_4F320(PLAYER *pPlayer)
+{
+ switch (pPlayer->atc3)
+ {
+ case 9:
+ pPlayer->at1ba = ClipHigh(divscale16(gFrameClock-pPlayer->at1b6,240), 65536);
+ pPlayer->atbf = 0;
+ if (!pPlayer->atc.buttonFlags.shoot)
+ {
+ pPlayer->atc3 = 8;
+ StartQAV(pPlayer, 29, nClientThrowProx, 0);
+ }
+ break;
+ }
+ return 0;
+}
+
+char sub_4F3A0(PLAYER *pPlayer)
+{
+ switch (pPlayer->atc3)
+ {
+ case 13:
+ pPlayer->at1ba = ClipHigh(divscale16(gFrameClock-pPlayer->at1b6,240), 65536);
+ if (!pPlayer->atc.buttonFlags.shoot)
+ {
+ pPlayer->atc3 = 11;
+ StartQAV(pPlayer, 39, nClientThrowRemote, 0);
+ }
+ break;
+ }
+ return 0;
+}
+
+char sub_4F414(PLAYER *pPlayer)
+{
+ switch (pPlayer->atc3)
+ {
+ case 4:
+ pPlayer->atc3 = 6;
+ StartQAV(pPlayer, 114, nClientFireLifeLeech, 1);
+ return 1;
+ case 6:
+ if (!pPlayer->atc.buttonFlags.shoot2)
+ {
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 118, -1, 0);
+ return 1;
+ }
+ break;
+ case 8:
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 118, -1, 0);
+ return 1;
+ }
+ return 0;
+}
+
+char sub_4F484(PLAYER *pPlayer)
+{
+ switch (pPlayer->atc3)
+ {
+ case 4:
+ pPlayer->atc3 = 5;
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 84, nClientFireTesla, 1);
+ else
+ StartQAV(pPlayer, 77, nClientFireTesla, 1);
+ return 1;
+ case 5:
+ if (!pPlayer->atc.buttonFlags.shoot)
+ {
+ pPlayer->atc3 = 2;
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 87, -1, 0);
+ else
+ StartQAV(pPlayer, 80, -1, 0);
+ return 1;
+ }
+ break;
+ case 7:
+ pPlayer->atc3 = 2;
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 87, -1, 0);
+ else
+ StartQAV(pPlayer, 80, -1, 0);
+ return 1;
+ }
+ return 0;
+}
+
+void WeaponProcess(PLAYER *pPlayer)
+{
+ pPlayer->at37b = ClipLow(pPlayer->at37b-1, 0);
+ if (pPlayer->pXSprite->health == 0)
+ {
+ pPlayer->at1b1 = 0;
+ sfxKill3DSound(pPlayer->pSprite, 1, -1);
+ }
+ if (pPlayer->at87 && BannedUnderwater(pPlayer->atbd))
+ {
+ if (sub_4B1A4(pPlayer))
+ {
+ if (pPlayer->atbd == 7)
+ {
+ pPlayer->at1b2 = pPlayer->atbf;
+ DropCan(1, pPlayer);
+ pPlayer->atc3 = 3;
+ }
+ else if (pPlayer->atbd == 6)
+ {
+ pPlayer->at1b2 = pPlayer->atbf;
+ DropBundle(1, pPlayer);
+ pPlayer->atc3 = 1;
+ }
+ }
+ WeaponLower(pPlayer);
+ pPlayer->at1ba = 0;
+ }
+ WeaponPlay(pPlayer);
+ UpdateAimVector(pPlayer);
+ pPlayer->atbf -= 4;
+ char bShoot = pPlayer->atc.buttonFlags.shoot;
+ char bShoot2 = pPlayer->atc.buttonFlags.shoot2;
+ if (pPlayer->at1b1 && pPlayer->pXSprite->health > 0)
+ {
+ if (bShoot && CheckAmmo(pPlayer, pPlayer->atc7, 1))
+ {
+ while (pPlayer->atbf <= 0)
+ pPlayer->atbf += weaponQAV[pPlayer->at26]->at10;
+ }
+ else
+ {
+ pPlayer->atbf = 0;
+ pPlayer->at1b1 = 0;
+ }
+ return;
+ }
+ pPlayer->atbf = ClipLow(pPlayer->atbf, 0);
+ switch (pPlayer->atbd)
+ {
+ case 7:
+ if (sub_4F0E0(pPlayer))
+ return;
+ break;
+ case 6:
+ if (sub_4F200(pPlayer))
+ return;
+ break;
+ case 11:
+ if (sub_4F320(pPlayer))
+ return;
+ break;
+ case 12:
+ if (sub_4F3A0(pPlayer))
+ return;
+ break;
+ }
+ if (pPlayer->atbf > 0)
+ return;
+ if (pPlayer->pXSprite->health == 0 || pPlayer->atbd == 0)
+ pPlayer->at26 = -1;
+ switch (pPlayer->atbd)
+ {
+ case 9:
+ if (sub_4F414(pPlayer))
+ return;
+ break;
+ case 8:
+ if (sub_4F484(pPlayer))
+ return;
+ break;
+ }
+ if (pPlayer->atbe)
+ {
+ sfxKill3DSound(pPlayer->pSprite, -1, 441);
+ pPlayer->atc3 = 0;
+ pPlayer->atc.newWeapon = pPlayer->atbe;
+ pPlayer->atbe = 0;
+ }
+ if (pPlayer->atc.keyFlags.nextWeapon)
+ {
+ pPlayer->atc.keyFlags.nextWeapon = 0;
+ pPlayer->atc3 = 0;
+ pPlayer->atbe = 0;
+ int t;
+ char weapon = WeaponFindNext(pPlayer, &t, 1);
+ pPlayer->atd9[weapon] = t;
+ if (pPlayer->atbd)
+ {
+ WeaponLower(pPlayer);
+ pPlayer->atbe = weapon;
+ return;
+ }
+ pPlayer->atc.newWeapon = weapon;
+ }
+ if (pPlayer->atc.keyFlags.prevWeapon)
+ {
+ pPlayer->atc.keyFlags.prevWeapon = 0;
+ pPlayer->atc3 = 0;
+ pPlayer->atbe = 0;
+ int t;
+ char weapon = WeaponFindNext(pPlayer, &t, 0);
+ pPlayer->atd9[weapon] = t;
+ if (pPlayer->atbd)
+ {
+ WeaponLower(pPlayer);
+ pPlayer->atbe = weapon;
+ return;
+ }
+ pPlayer->atc.newWeapon = weapon;
+ }
+ if (pPlayer->atc3 == -1)
+ {
+ pPlayer->atc3 = 0;
+ int t;
+ char weapon = WeaponFindLoaded(pPlayer, &t);
+ pPlayer->atd9[weapon] = t;
+ if (pPlayer->atbd)
+ {
+ WeaponLower(pPlayer);
+ pPlayer->atbe = weapon;
+ return;
+ }
+ pPlayer->atc.newWeapon = weapon;
+ }
+ if (pPlayer->atc.newWeapon)
+ {
+ if (pPlayer->atc.newWeapon == 6)
+ {
+ if (pPlayer->atbd == 6)
+ {
+ if (sub_4B2C8(pPlayer, 10, 1))
+ pPlayer->atc.newWeapon = 11;
+ else if (sub_4B2C8(pPlayer, 11, 1))
+ pPlayer->atc.newWeapon = 12;
+ }
+ else if (pPlayer->atbd == 11)
+ {
+ if (sub_4B2C8(pPlayer, 11, 1))
+ pPlayer->atc.newWeapon = 12;
+ else if (sub_4B2C8(pPlayer, 5, 1) && pPlayer->at87 == 0)
+ pPlayer->atc.newWeapon = 6;
+ }
+ else if (pPlayer->atbd == 12)
+ {
+ if (sub_4B2C8(pPlayer, 5, 1) && pPlayer->at87 == 0)
+ pPlayer->atc.newWeapon = 6;
+ else if (sub_4B2C8(pPlayer, 10, 1))
+ pPlayer->atc.newWeapon = 11;
+ }
+ else
+ {
+ if (sub_4B2C8(pPlayer, 5, 1) && pPlayer->at87 == 0)
+ pPlayer->atc.newWeapon = 6;
+ else if (sub_4B2C8(pPlayer, 10, 1))
+ pPlayer->atc.newWeapon = 11;
+ else if (sub_4B2C8(pPlayer, 11, 1))
+ pPlayer->atc.newWeapon = 12;
+ }
+ }
+ if (pPlayer->pXSprite->health == 0 || pPlayer->atcb[pPlayer->atc.newWeapon] == 0)
+ {
+ pPlayer->atc.newWeapon = 0;
+ return;
+ }
+ if (pPlayer->at87 && BannedUnderwater(pPlayer->atc.newWeapon) && !sub_4B1A4(pPlayer))
+ {
+ pPlayer->atc.newWeapon = 0;
+ return;
+ }
+ int nWeapon = pPlayer->atc.newWeapon;
+ int v4c = weaponModes[nWeapon].at0;
+ if (!pPlayer->atbd)
+ {
+ int nAmmoType = weaponModes[nWeapon].at4;
+ if (v4c > 1)
+ {
+ if (CheckAmmo(pPlayer, nAmmoType, 1) || nAmmoType == 11)
+ WeaponRaise(pPlayer);
+ pPlayer->atc.newWeapon = 0;
+ }
+ else
+ {
+ if (sub_4B1FC(pPlayer, nWeapon, nAmmoType, 1))
+ WeaponRaise(pPlayer);
+ else
+ {
+ pPlayer->atc3 = 0;
+ int t;
+ char weapon = WeaponFindLoaded(pPlayer, &t);
+ pPlayer->atd9[weapon] = t;
+ if (pPlayer->atbd)
+ {
+ WeaponLower(pPlayer);
+ pPlayer->atbe = weapon;
+ return;
+ }
+ pPlayer->atc.newWeapon = weapon;
+ }
+ }
+ return;
+ }
+ if (nWeapon == pPlayer->atbd && v4c <= 1)
+ {
+ pPlayer->atc.newWeapon = 0;
+ return;
+ }
+ int i = 0;
+ if (nWeapon == pPlayer->atbd)
+ i = 1;
+ for (; i <= v4c; i++)
+ {
+ int v6c = (pPlayer->atd9[nWeapon]+i)%v4c;
+ if (sub_4B1FC(pPlayer, nWeapon, weaponModes[nWeapon].at4, 1))
+ {
+ WeaponLower(pPlayer);
+ pPlayer->atd9[nWeapon] = v6c;
+ return;
+ }
+ }
+ pPlayer->atc.newWeapon = 0;
+ return;
+ }
+ if (pPlayer->atbd && !CheckAmmo(pPlayer, pPlayer->atc7, 1) && pPlayer->atc7 != 11)
+ {
+ pPlayer->atc3 = -1;
+ return;
+ }
+ if (bShoot)
+ {
+ switch (pPlayer->atbd)
+ {
+ case 1:
+ StartQAV(pPlayer, 2, nClientFirePitchfork, 0);
+ return;
+ case 7:
+ switch (pPlayer->atc3)
+ {
+ case 3:
+ pPlayer->atc3 = 4;
+ StartQAV(pPlayer, 10, nClientFireSpray, 1);
+ return;
+ }
+ break;
+ case 6:
+ switch (pPlayer->atc3)
+ {
+ case 3:
+ pPlayer->atc3 = 6;
+ pPlayer->at1b2 = -1;
+ pPlayer->at1b6 = gFrameClock;
+ StartQAV(pPlayer, 21, nClientExplodeBundle, 0);
+ return;
+ }
+ break;
+ case 11:
+ switch (pPlayer->atc3)
+ {
+ case 7:
+ pPlayer->at26 = 27;
+ pPlayer->atc3 = 9;
+ pPlayer->at1b6 = gFrameClock;
+ return;
+ }
+ break;
+ case 12:
+ switch (pPlayer->atc3)
+ {
+ case 10:
+ pPlayer->at26 = 36;
+ pPlayer->atc3 = 13;
+ pPlayer->at1b6 = gFrameClock;
+ return;
+ case 11:
+ pPlayer->atc3 = 12;
+ StartQAV(pPlayer, 40, nClientFireRemote, 0);
+ return;
+ }
+ break;
+ case 3:
+ switch (pPlayer->atc3)
+ {
+ case 7:
+ pPlayer->atc3 = 6;
+ StartQAV(pPlayer, 61, nClientFireShotgun, 0);
+ return;
+ case 3:
+ pPlayer->atc3 = 2;
+ StartQAV(pPlayer, 54, nClientFireShotgun, 0);
+ return;
+ case 2:
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 55, nClientFireShotgun, 0);
+ return;
+ }
+ break;
+ case 4:
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 3, 2))
+ StartQAV(pPlayer, 71, nClientFireTommy, 1);
+ else
+ StartQAV(pPlayer, 66, nClientFireTommy, 1);
+ return;
+ case 2:
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 1, 2))
+ StartQAV(pPlayer, 48, nClientFireFlare, 0);
+ else
+ StartQAV(pPlayer, 43, nClientFireFlare, 0);
+ return;
+ case 10:
+ {
+ static int nChance[] = { 0xa000, 0xc000, 0xe000, 0x10000 };
+ int nRand = wrand()*2;
+ int i;
+ for (i = 0; nChance[i] < nRand; i++)
+ {
+ }
+ pPlayer->voodooTarget = pPlayer->at1d6;
+ if (pPlayer->voodooTarget == -1 || sprite[pPlayer->voodooTarget].statnum != 6)
+ i = 4;
+ StartQAV(pPlayer,103+i, nClientFireVoodoo, 0);
+ return;
+ }
+ case 8:
+ switch (pPlayer->atc3)
+ {
+ case 2:
+ pPlayer->atc3 = 4;
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 84, nClientFireTesla, 0);
+ else
+ StartQAV(pPlayer, 77, nClientFireTesla, 0);
+ return;
+ case 5:
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 84, nClientFireTesla, 0);
+ else
+ StartQAV(pPlayer, 77, nClientFireTesla, 0);
+ return;
+ }
+ break;
+ case 5:
+ if (powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 122, nClientFireNapalm, 0);
+ else
+ StartQAV(pPlayer, 91, nClientFireNapalm, 0);
+ return;
+ case 9:
+ sfxPlay3DSound(pPlayer->pSprite, 494, 2, 0);
+ StartQAV(pPlayer, 116, nClientFireLifeLeech, 0);
+ return;
+ case 13:
+ StartQAV(pPlayer, 95+Random(4), nClientFireBeast, 0);
+ return;
+ }
+ }
+ if (bShoot2)
+ {
+ switch (pPlayer->atbd)
+ {
+ case 1:
+ StartQAV(pPlayer, 2, nClientFirePitchfork, 0);
+ return;
+ case 7:
+ switch (pPlayer->atc3)
+ {
+ case 3:
+ pPlayer->atc3 = 5;
+ StartQAV(pPlayer, 12, nClientExplodeCan, 0);
+ return;
+ }
+ break;
+ case 6:
+ switch (pPlayer->atc3)
+ {
+ case 3:
+ pPlayer->atc3 = 4;
+ StartQAV(pPlayer, 21, nClientExplodeBundle, 0);
+ return;
+ case 7:
+ pPlayer->atc3 = 8;
+ StartQAV(pPlayer, 28, nClientDropProx, 0);
+ return;
+ case 10:
+ pPlayer->atc3 = 11;
+ StartQAV(pPlayer, 38, nClientDropRemote, 0);
+ return;
+ case 11:
+ if (pPlayer->at181[11] > 0)
+ {
+ pPlayer->atc3 = 10;
+ StartQAV(pPlayer, 30, -1, 0);
+ }
+ return;
+ }
+ break;
+ case 11:
+ switch (pPlayer->atc3)
+ {
+ case 7:
+ pPlayer->atc3 = 8;
+ StartQAV(pPlayer, 28, nClientDropProx, 0);
+ return;
+ }
+ break;
+ case 12:
+ switch (pPlayer->atc3)
+ {
+ case 10:
+ pPlayer->atc3 = 11;
+ StartQAV(pPlayer, 38, nClientDropRemote, 0);
+ return;
+ case 11:
+ if (pPlayer->at181[11] > 0)
+ {
+ pPlayer->atc3 = 10;
+ StartQAV(pPlayer, 30, -1, 0);
+ }
+ return;
+ }
+ break;
+ case 3:
+ switch (pPlayer->atc3)
+ {
+ case 7:
+ pPlayer->atc3 = 6;
+ StartQAV(pPlayer, 62, nClientFireShotgun, 0);
+ return;
+ case 3:
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 56, nClientFireShotgun, 0);
+ return;
+ case 2:
+ pPlayer->atc3 = 1;
+ StartQAV(pPlayer, 55, nClientFireShotgun, 0);
+ return;
+ }
+ break;
+ case 4:
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 3, 2))
+ StartQAV(pPlayer, 73, nClientAltFireSpread2, 0);
+ else
+ StartQAV(pPlayer, 67, nClientAltFireSpread2, 0);
+ return;
+ case 10:
+ sfxPlay3DSound(pPlayer->pSprite, 461, 2, 0);
+ StartQAV(pPlayer, 110, nClientAltFireVoodoo, 0);
+ return;
+#if 0
+ case 2:
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 1, 2))
+ StartQAV(pPlayer, 48, nClientFireFlare, 0);
+ else
+ StartQAV(pPlayer, 43, nClientFireFlare, 0);
+ return;
+#endif
+ case 8:
+ if (sub_4B2C8(pPlayer, 7, 35))
+ {
+ if (sub_4B2C8(pPlayer, 7, 70) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 85, nClientFireTesla, 0);
+ else
+ StartQAV(pPlayer, 78, nClientFireTesla, 0);
+ }
+ else
+ {
+ if (sub_4B2C8(pPlayer, 7, 10) && powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 84, nClientFireTesla, 0);
+ else
+ StartQAV(pPlayer, 77, nClientFireTesla, 0);
+ }
+ return;
+ case 5:
+ if (powerupCheck(pPlayer, 17))
+ StartQAV(pPlayer, 122, nClientAltFireNapalm, 0);
+ else
+ StartQAV(pPlayer, 91, nClientAltFireNapalm, 0);
+ return;
+ case 2:
+ if (CheckAmmo(pPlayer, 1, 8))
+ {
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 1, 16))
+ StartQAV(pPlayer, 48, nClientAltFireFlare, 0);
+ else
+ StartQAV(pPlayer, 43, nClientAltFireFlare, 0);
+ }
+ else
+ {
+ if (powerupCheck(pPlayer, 17) && sub_4B2C8(pPlayer, 1, 2))
+ StartQAV(pPlayer, 48, nClientFireFlare, 0);
+ else
+ StartQAV(pPlayer, 43, nClientFireFlare, 0);
+ }
+ return;
+ case 9:
+ if (gGameOptions.nGameType <= 1 && !sub_4B2C8(pPlayer, 8, 1) && pPlayer->pXSprite->health < (25 << 4))
+ {
+ sfxPlay3DSound(pPlayer->pSprite, 494, 2, 0);
+ StartQAV(pPlayer, 116, nClientFireLifeLeech, 0);
+ }
+ else
+ {
+ StartQAV(pPlayer, 119, -1, 0);
+ AltFireLifeLeech(1, pPlayer);
+ pPlayer->atc3 = -1;
+ }
+ return;
+ }
+ }
+ WeaponUpdateState(pPlayer);
+}
+
+void sub_51340(spritetype *pMissile, int a2)
+{
+ char va4[(kMaxSectors+7)>>3];
+ int x = pMissile->x;
+ int y = pMissile->y;
+ int z = pMissile->z;
+ int nDist = 300;
+ int nSector = pMissile->sectnum;
+ int nOwner = actSpriteOwnerToSpriteId(pMissile);
+ gAffectedSectors[0] = -1;
+ gAffectedXWalls[0] = -1;
+ GetClosestSpriteSectors(nSector, x, y, nDist, gAffectedSectors, va4, gAffectedXWalls);
+ char v4 = 1;
+ int v24 = -1;
+ actHitcodeToData(a2, &gHitInfo, &v24, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+ if (a2 == 3 && v24 >= 0 && sprite[v24].statnum == 6)
+ v4 = 0;
+ for (int nSprite = headspritestat[6]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ if (nSprite != nOwner || v4)
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ if (TestBitString(va4, pSprite->sectnum) && CheckProximity(pSprite, x, y, z, nSector, nDist))
+ {
+ int dx = pMissile->x-pSprite->x;
+ int dy = pMissile->y-pSprite->y;
+ int nDamage = ClipLow((nDist-(ksqrt(dx*dx+dy*dy)>>4)+20)>>1, 10);
+ if (nSprite == nOwner)
+ nDamage /= 2;
+ actDamageSprite(nOwner, pSprite, DAMAGE_TYPE_6, nDamage<<4);
+ }
+ }
+ }
+ for (int nSprite = headspritestat[4]; nSprite >= 0; nSprite = nextspritestat[nSprite])
+ {
+ spritetype *pSprite = &sprite[nSprite];
+ if (pSprite->hitag&32)
+ continue;
+ if (TestBitString(va4, pSprite->sectnum) && CheckProximity(pSprite, x, y, z, nSector, nDist))
+ {
+ XSPRITE *pXSprite = &xsprite[pSprite->extra];
+ if (!pXSprite->locked)
+ {
+ int dx = pMissile->x-pSprite->x;
+ int dy = pMissile->y-pSprite->y;
+ int nDamage = ClipLow(nDist-(ksqrt(dx*dx+dy*dy)>>4)+20, 20);
+ actDamageSprite(nOwner, pSprite, DAMAGE_TYPE_6, nDamage<<4);
+ }
+ }
+ }
+}
+
+class WeaponLoadSave : public LoadSave
+{
+public:
+ virtual void Load();
+ virtual void Save();
+};
+
+void WeaponLoadSave::Load()
+{
+}
+
+void WeaponLoadSave::Save()
+{
+}
+
+static WeaponLoadSave *myLoadSave;
+
+void WeaponLoadSaveConstruct(void)
+{
+ myLoadSave = new WeaponLoadSave();
+}
+
diff --git a/source/blood/src/weapon.h b/source/blood/src/weapon.h
new file mode 100644
index 000000000..b2a0ce01b
--- /dev/null
+++ b/source/blood/src/weapon.h
@@ -0,0 +1,35 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+#pragma once
+#include "common_game.h"
+#include "blood.h"
+#include "db.h"
+#include "player.h"
+
+void WeaponInit(void);
+void WeaponDraw(PLAYER *pPlayer, int a2, int a3, int a4, int a5);
+void WeaponRaise(PLAYER *pPlayer);
+void WeaponLower(PLAYER *pPlayer);
+char WeaponUpgrade(PLAYER *pPlayer, char newWeapon);
+void WeaponProcess(PLAYER *pPlayer);
+void sub_51340(spritetype *pMissile, int a2);
\ No newline at end of file
diff --git a/source/blood/src/winbits.cpp b/source/blood/src/winbits.cpp
new file mode 100644
index 000000000..235c86157
--- /dev/null
+++ b/source/blood/src/winbits.cpp
@@ -0,0 +1,132 @@
+//-------------------------------------------------------------------------
+/*
+Copyright (C) 2010-2019 EDuke32 developers and contributors
+Copyright (C) 2019 Nuke.YKT
+
+This file is part of NBlood.
+
+NBlood is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License version 2
+as published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+*/
+//-------------------------------------------------------------------------
+
+#ifdef _WIN32
+
+#include "compat.h"
+
+#define NEED_SHELLAPI_H
+#define NEED_WINSOCK2_H
+#define NEED_WS2TCPIP_H
+#include "windows_inc.h"
+
+#include "renderlayer.h"
+
+int32_t G_GetVersionFromWebsite(char *buffer)
+{
+ static int32_t wsainitialized = 0;
+ int32_t i=0, j=0, r=0;
+ struct sockaddr_in dest_addr;
+ struct hostent *h;
+ char const *host = "www.eduke32.com";
+ char const *req = "GET http://www.eduke32.com/VERSION HTTP/1.0\r\n\r\n\r\n";
+ char *tok;
+ char tempbuf[2048],otherbuf[16],ver[16];
+ SOCKET mysock;
+ WSADATA ws;
+
+#ifdef _WIN32
+ if (wsainitialized == 0)
+ {
+ if (WSAStartup(0x101, &ws) == SOCKET_ERROR)
+ return 0;
+
+ wsainitialized = 1;
+ }
+#endif
+
+ if ((h = gethostbyname(host)) == NULL)
+ {
+ initprintf("Couldn't resolve %s!\n", host);
+ return 0;
+ }
+
+ dest_addr.sin_addr.s_addr = ((struct in_addr *)(h->h_addr))->s_addr;
+ dest_addr.sin_family = AF_INET;
+ dest_addr.sin_port = htons(80);
+
+ memset(&(dest_addr.sin_zero), '\0', 8);
+
+ mysock = socket(PF_INET, SOCK_STREAM, 0);
+
+ if (mysock == INVALID_SOCKET)
+ {
+ WSACleanup();
+ return 0;
+ }
+
+ initprintf("Connecting to http://%s\n",host);
+
+ if (connect(mysock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)) == SOCKET_ERROR)
+ goto done;
+
+ i = send(mysock, req, strlen(req), 0);
+
+ if (i == SOCKET_ERROR)
+ goto done;
+
+ i = recv(mysock, (char *)&tempbuf, sizeof(tempbuf), 0);
+
+ if (i < 0)
+ goto done;
+
+ Bmemcpy(&otherbuf, &tempbuf, sizeof(otherbuf));
+
+ strtok(otherbuf, " ");
+
+ if ((tok = strtok(NULL, " ")) == NULL)
+ goto done;
+
+ if (Batol(tok) == 200)
+ {
+ for (i = 0; (unsigned)i < strlen(tempbuf); i++) // HACK: all of this needs to die a fiery death; we just skip to the content
+ {
+ // instead of actually parsing any of the http headers
+ if (i > 4)
+ if (tempbuf[i-1] == '\n' && tempbuf[i-2] == '\r' && tempbuf[i-3] == '\n' && tempbuf[i-4] == '\r')
+ {
+ while (j < 9)
+ {
+ ver[j] = tempbuf[i];
+ i++, j++;
+ }
+ ver[j] = '\0';
+ break;
+ }
+ }
+
+ if (j)
+ {
+ strcpy(buffer, ver);
+ r = 1;
+ goto done;
+ }
+ }
+
+done:
+ closesocket(mysock);
+ WSACleanup();
+
+ return r;
+}
+#endif
diff --git a/source/build/include/clip.h b/source/build/include/clip.h
index 696a43bf2..0fdc288e0 100644
--- a/source/build/include/clip.h
+++ b/source/build/include/clip.h
@@ -46,6 +46,8 @@ extern "C" {
#define CM_NOROT(Spri) (sprite[Spri].cstat&2)
#define CM_NOROTS(Sect) (sector[Sect].CM_CSTAT&2)
+extern vec2_t hitscangoal;
+
typedef struct
{
int16_t qbeg, qend; // indices into sectq
diff --git a/source/build/src/clip.cpp b/source/build/src/clip.cpp
index 6f9691c0f..0b469dcff 100644
--- a/source/build/src/clip.cpp
+++ b/source/build/src/clip.cpp
@@ -61,7 +61,7 @@ static usectortype *loadsector;
static uwalltype *loadwall, *loadwallinv;
static uspritetype *loadsprite;
-static vec2_t const hitscangoal = { (1<<29)-1, (1<<29)-1 };
+vec2_t hitscangoal = { (1<<29)-1, (1<<29)-1 };
#ifdef USE_OPENGL
int32_t hitallsprites = 0;
#endif
diff --git a/source/build/src/tiles.cpp b/source/build/src/tiles.cpp
index 9d4c494fc..f14c19617 100644
--- a/source/build/src/tiles.cpp
+++ b/source/build/src/tiles.cpp
@@ -49,7 +49,6 @@ static buildvfs_kfd artfil;
////////// Per-map ART file loading //////////
// Some forward declarations.
-static void tileUpdatePicSiz(int32_t picnum);
static const char *artGetIndexedFileName(int32_t tilefilei);
static int32_t artReadIndexedFile(int32_t tilefilei);
@@ -267,7 +266,7 @@ void tileDelete(int32_t const tile)
#endif
}
-static void tileUpdatePicSiz(int32_t picnum)
+void tileUpdatePicSiz(int32_t picnum)
{
int j = 15;
diff --git a/source/libsmackerdec/COPYING b/source/libsmackerdec/COPYING
new file mode 100644
index 000000000..00b4fedfe
--- /dev/null
+++ b/source/libsmackerdec/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ , 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/source/libsmackerdec/include/BitReader.h b/source/libsmackerdec/include/BitReader.h
new file mode 100644
index 000000000..39a1511f3
--- /dev/null
+++ b/source/libsmackerdec/include/BitReader.h
@@ -0,0 +1,54 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SmackerBitReader_h_
+#define _SmackerBitReader_h_
+
+#include
+#include "FileStream.h"
+
+namespace SmackerCommon {
+
+class BitReader
+{
+ public:
+ BitReader(SmackerCommon::FileStream &file, uint32_t size);
+ ~BitReader();
+ uint32_t GetBit();
+ uint32_t GetBits(uint32_t n);
+ void SkipBits(uint32_t n);
+
+ uint32_t GetSize();
+ uint32_t GetPosition();
+
+ private:
+ uint32_t totalSize;
+ uint32_t currentOffset;
+ uint32_t bytesRead;
+
+ SmackerCommon::FileStream *file;
+
+ uint8_t *cache;
+
+ void FillCache();
+};
+
+} // close namespace SmackerCommon
+
+#endif
\ No newline at end of file
diff --git a/source/libsmackerdec/include/FileStream.h b/source/libsmackerdec/include/FileStream.h
new file mode 100644
index 000000000..7fe408466
--- /dev/null
+++ b/source/libsmackerdec/include/FileStream.h
@@ -0,0 +1,66 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SmackerFileStream_h_
+#define _SmackerFileStream_h_
+
+#include
+#include "cache1d.h"
+#include "compat.h"
+#include
+
+namespace SmackerCommon {
+
+class FileStream
+{
+ public:
+
+ bool Open(const std::string &fileName);
+ bool Is_Open();
+ void Close();
+
+ int32_t ReadBytes(uint8_t *data, uint32_t nBytes);
+
+ uint32_t ReadUint32LE();
+ uint32_t ReadUint32BE();
+
+ uint16_t ReadUint16LE();
+ uint16_t ReadUint16BE();
+
+ uint8_t ReadByte();
+
+ enum SeekDirection{
+ kSeekCurrent = 0,
+ kSeekStart = 1,
+ kSeekEnd = 2
+ };
+
+ bool Seek(int32_t offset, SeekDirection = kSeekStart);
+ bool Skip(int32_t offset);
+
+ int32_t GetPosition();
+ bool Is_Eos();
+
+ private:
+ int file;
+};
+
+} // close namespace SmackerCommon
+
+#endif
diff --git a/source/libsmackerdec/include/HuffmanVLC.h b/source/libsmackerdec/include/HuffmanVLC.h
new file mode 100644
index 000000000..668249317
--- /dev/null
+++ b/source/libsmackerdec/include/HuffmanVLC.h
@@ -0,0 +1,43 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SmackerHuffmanVLC_h_
+#define _SmackerHuffmanVLC_h_
+
+#include
+#include "BitReader.h"
+#include
+
+namespace SmackerCommon {
+
+struct VLC
+{
+ uint32_t symbol;
+ uint32_t code;
+};
+
+typedef std::vector< std::vector > VLCtable;
+
+uint16_t VLC_GetCodeBits(BitReader &bits, VLCtable &table);
+void VLC_InitTable (VLCtable &table, uint32_t maxLength, uint32_t size, int *lengths, uint32_t *bits);
+uint32_t VLC_GetSize (VLCtable &table);
+
+} // close namespace SmackerCommon
+
+#endif
diff --git a/source/libsmackerdec/include/LogError.h b/source/libsmackerdec/include/LogError.h
new file mode 100644
index 000000000..95a572961
--- /dev/null
+++ b/source/libsmackerdec/include/LogError.h
@@ -0,0 +1,31 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SmackerLogError_h_
+#define _SmackerLogError_h_
+
+#include
+
+namespace SmackerCommon {
+
+void LogError(const std::string &error);
+
+} // close namespace SmackerCommon
+
+#endif
\ No newline at end of file
diff --git a/source/libsmackerdec/include/SmackerDecoder.h b/source/libsmackerdec/include/SmackerDecoder.h
new file mode 100644
index 000000000..feb719571
--- /dev/null
+++ b/source/libsmackerdec/include/SmackerDecoder.h
@@ -0,0 +1,167 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* This code is based on smacker.c from the FFmpeg project which can be obtained from http://www.ffmpeg.org/
+ * below is the license from smacker.c
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*
+ * Smacker decoder
+ * Copyright (c) 2006 Konstantin Shishkov
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _SmackerDecoder_h_
+#define _SmackerDecoder_h_
+
+#include
+#include "FileStream.h"
+#include "BitReader.h"
+#include
+
+// exportable interface
+struct SmackerHandle
+{
+ bool isValid;
+ int instanceIndex;
+};
+
+struct SmackerAudioInfo
+{
+ uint32_t sampleRate;
+ uint8_t nChannels;
+ uint8_t bitsPerSample;
+
+ uint32_t idealBufferSize;
+};
+
+SmackerHandle Smacker_Open (const char* fileName);
+void Smacker_Close (SmackerHandle &handle);
+uint32_t Smacker_GetNumAudioTracks (SmackerHandle &handle);
+SmackerAudioInfo Smacker_GetAudioTrackDetails (SmackerHandle &handle, uint32_t trackIndex);
+uint32_t Smacker_GetAudioData (SmackerHandle &handle, uint32_t trackIndex, int16_t *data);
+uint32_t Smacker_GetNumFrames (SmackerHandle &handle);
+void Smacker_GetFrameSize (SmackerHandle &handle, uint32_t &width, uint32_t &height);
+uint32_t Smacker_GetCurrentFrameNum (SmackerHandle &handle);
+uint32_t Smacker_GetNextFrame (SmackerHandle &handle);
+float Smacker_GetFrameRate (SmackerHandle &handle);
+void Smacker_GetPalette (SmackerHandle &handle, uint8_t *palette);
+void Smacker_GetFrame (SmackerHandle &handle, uint8_t *frame);
+void Smacker_GotoFrame (SmackerHandle &handle, uint32_t frameNum);
+
+const int kMaxAudioTracks = 7;
+
+// forward declare
+struct HuffContext;
+struct DBCtx;
+
+struct SmackerAudioTrack
+{
+ uint32_t sizeInBytes;
+ uint32_t flags;
+ uint32_t sampleRate;
+ uint8_t nChannels;
+ uint8_t bitsPerSample;
+// int compressionType;
+
+ uint8_t *buffer;
+ uint32_t bufferSize;
+
+ uint32_t bytesReadThisFrame;
+};
+
+class SmackerDecoder
+{
+ public:
+ uint32_t frameWidth;
+ uint32_t frameHeight;
+
+ SmackerDecoder();
+ ~SmackerDecoder();
+
+ bool Open(const std::string &fileName);
+ void GetPalette(uint8_t *palette);
+ void GetFrame(uint8_t *frame);
+
+ SmackerAudioInfo GetAudioTrackDetails(uint32_t trackIndex);
+ uint32_t GetAudioData(uint32_t trackIndex, int16_t *audioBuffer);
+ uint32_t GetNumFrames();
+ uint32_t GetCurrentFrameNum();
+ float GetFrameRate();
+ void GetNextFrame();
+ void GotoFrame(uint32_t frameNum);
+
+ private:
+ SmackerCommon::FileStream file;
+ char signature[4];
+
+ // video related members
+ uint32_t nFrames;
+ uint32_t fps; // frames per second
+
+ uint8_t palette[768];
+ uint8_t *picture;
+
+ bool isVer4;
+
+ SmackerAudioTrack audioTracks[kMaxAudioTracks];
+
+ uint32_t treeSize;
+ uint32_t mMapSize, MClrSize, fullSize, typeSize;
+
+ std::vector mmap_tbl;
+ std::vector mclr_tbl;
+ std::vector full_tbl;
+ std::vector type_tbl;
+
+ int mmap_last[3], mclr_last[3], full_last[3], type_last[3];
+
+ std::vector frameSizes;
+ std::vector frameFlags;
+
+ uint32_t currentFrame;
+ int32_t nextPos;
+
+ bool DecodeHeaderTrees();
+ int DecodeHeaderTree(SmackerCommon::BitReader &bits, std::vector &recodes, int *last, int size);
+ int DecodeTree(SmackerCommon::BitReader &bits, HuffContext *hc, uint32_t prefix, int length);
+ int DecodeBigTree(SmackerCommon::BitReader &bits, HuffContext *hc, DBCtx *ctx);
+ int GetCode(SmackerCommon::BitReader &bits, std::vector &recode, int *last);
+ int ReadPacket();
+ int DecodeFrame(uint32_t frameSize);
+ void GetFrameSize(uint32_t &width, uint32_t &height);
+ int DecodeAudio(uint32_t size, SmackerAudioTrack &track);
+};
+
+#endif
diff --git a/source/libsmackerdec/src/BitReader.cpp b/source/libsmackerdec/src/BitReader.cpp
new file mode 100644
index 000000000..81afa947d
--- /dev/null
+++ b/source/libsmackerdec/src/BitReader.cpp
@@ -0,0 +1,91 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "BitReader.h"
+#include
+
+namespace SmackerCommon {
+
+BitReader::BitReader(SmackerCommon::FileStream &file, uint32_t size)
+{
+ this->file = &file;
+ this->totalSize = size;
+ this->currentOffset = 0;
+ this->bytesRead = 0;
+
+ this->cache = (uint8_t*)Xmalloc(size);
+ file.ReadBytes(this->cache, size);
+}
+
+BitReader::~BitReader()
+{
+ Bfree(this->cache);
+}
+
+void BitReader::FillCache()
+{
+}
+
+uint32_t BitReader::GetSize()
+{
+ return totalSize * 8;
+}
+
+uint32_t BitReader::GetPosition()
+{
+ return currentOffset;
+}
+
+uint32_t BitReader::GetBit()
+{
+ uint32_t ret = (cache[currentOffset>>3]>>(currentOffset&7))&1;
+ currentOffset++;
+ return ret;
+}
+
+uint32_t BitReader::GetBits(uint32_t n)
+{
+ uint32_t ret = 0;
+
+ int bitsTodo = n;
+
+ uint32_t theShift = 0;
+
+ while (bitsTodo)
+ {
+ uint32_t bit = GetBit();
+ bit <<= theShift;
+
+ theShift++;
+
+ ret |= bit;
+
+ bitsTodo--;
+ }
+
+ return ret;
+}
+
+void BitReader::SkipBits(uint32_t n)
+{
+ GetBits(n);
+}
+
+} // close namespace SmackerCommon
+
diff --git a/source/libsmackerdec/src/FileStream.cpp b/source/libsmackerdec/src/FileStream.cpp
new file mode 100644
index 000000000..414c68f4c
--- /dev/null
+++ b/source/libsmackerdec/src/FileStream.cpp
@@ -0,0 +1,131 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "FileStream.h"
+#include
+
+namespace SmackerCommon {
+
+bool FileStream::Open(const std::string &fileName)
+{
+ file = kopen4loadfrommod(fileName.c_str(), 0);
+ if (file == -1)
+ {
+ // log error
+ return false;
+ }
+
+ return true;
+}
+
+bool FileStream::Is_Open()
+{
+ return file != -1;
+}
+
+void FileStream::Close()
+{
+ kclose(file);
+ file = -1;
+}
+
+int32_t FileStream::ReadBytes(uint8_t *data, uint32_t nBytes)
+{
+ uint32_t nCount = (uint32_t)kread(file, data, static_cast(nBytes));
+
+ if (nCount != nBytes)
+ {
+ return 0;
+ }
+
+ return (int32_t)nCount;
+}
+
+uint32_t FileStream::ReadUint32LE()
+{
+ uint32_t value;
+ kread(file, &value, 4);
+ return B_LITTLE32(value);
+}
+
+uint32_t FileStream::ReadUint32BE()
+{
+ uint32_t value;
+ kread(file, &value, 4);
+ return B_BIG32(value);
+}
+
+uint16_t FileStream::ReadUint16LE()
+{
+ uint16_t value;
+ kread(file, &value, 2);
+ return B_LITTLE16(value);
+}
+
+uint16_t FileStream::ReadUint16BE()
+{
+ uint16_t value;
+ kread(file, &value, 2);
+ return B_BIG16(value);
+}
+
+uint8_t FileStream::ReadByte()
+{
+ uint8_t value;
+ kread(file, &value, 1);
+ return value;
+}
+
+bool FileStream::Seek(int32_t offset, SeekDirection direction)
+{
+ int32_t nStatus = -1;
+ if (kSeekStart == direction) {
+ nStatus = klseek(file, offset, SEEK_SET);
+ }
+ else if (kSeekCurrent == direction) {
+ nStatus = klseek(file, offset, SEEK_CUR);
+ }
+
+ // TODO - end seek
+ if (nStatus < 0)
+ {
+ // todo
+ return false;
+ }
+
+ return true;
+}
+
+bool FileStream::Skip(int32_t offset)
+{
+ return Seek(offset, kSeekCurrent);
+}
+
+bool FileStream::Is_Eos()
+{
+ // TODO:
+ return false;
+}
+
+int32_t FileStream::GetPosition()
+{
+ return ktell(file);
+}
+
+} // close namespace SmackerCommon
diff --git a/source/libsmackerdec/src/HuffmanVLC.cpp b/source/libsmackerdec/src/HuffmanVLC.cpp
new file mode 100644
index 000000000..3f7fd45f8
--- /dev/null
+++ b/source/libsmackerdec/src/HuffmanVLC.cpp
@@ -0,0 +1,71 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include
+
+namespace SmackerCommon {
+
+uint16_t VLC_GetCodeBits(BitReader &bits, VLCtable &table)
+{
+ uint32_t codeBits = 0;
+
+ // search each length array
+ for (uint32_t i = 0; i < table.size(); i++)
+ {
+ // get and add a new bit to codeBits
+ uint32_t theBit = bits.GetBit() << i;
+ codeBits |= theBit;
+
+ // search for a code match
+ for (uint32_t j = 0; j < table[i].size(); j++)
+ {
+ if (codeBits == table[i][j].code)
+ {
+ return table[i][j].symbol;
+ }
+ }
+ }
+
+ // shouldn't get here..
+ return 0;
+}
+
+void VLC_InitTable(VLCtable &table, uint32_t maxLength, uint32_t size, int *lengths, uint32_t *bits)
+{
+ table.resize(maxLength);
+
+ for (uint32_t i = 0; i < size; i++)
+ {
+ VLC newCode;
+ newCode.symbol = i;
+ newCode.code = bits[i];
+
+ uint32_t codeLength = lengths[i];
+
+ if (codeLength)
+ table[codeLength - 1].push_back(newCode);
+ }
+}
+
+uint32_t VLC_GetSize(VLCtable &table)
+{
+ return table.size();
+}
+
+} // close namespace SmackerCommon
diff --git a/source/libsmackerdec/src/LogError.cpp b/source/libsmackerdec/src/LogError.cpp
new file mode 100644
index 000000000..82a648a41
--- /dev/null
+++ b/source/libsmackerdec/src/LogError.cpp
@@ -0,0 +1,31 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "LogError.h"
+
+namespace SmackerCommon {
+
+static std::string LastError;
+
+void LogError(const std::string &error)
+{
+ LastError = error;
+}
+
+} // close namespace SmackerCommon
diff --git a/source/libsmackerdec/src/SmackerDecoder.cpp b/source/libsmackerdec/src/SmackerDecoder.cpp
new file mode 100644
index 000000000..e280bcde3
--- /dev/null
+++ b/source/libsmackerdec/src/SmackerDecoder.cpp
@@ -0,0 +1,1157 @@
+/*
+ * libsmackerdec - Smacker video decoder
+ * Copyright (C) 2011 Barry Duncan
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* This code is heavily based on smacker.c from the FFmpeg project which can be obtained from http://www.ffmpeg.org/
+ * below is the license from smacker.c
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*
+ * Smacker decoder
+ * Copyright (c) 2006 Konstantin Shishkov
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "SmackerDecoder.h"
+#include "HuffmanVLC.h"
+#include "LogError.h"
+#include
+#include
+#include
+#include "compat.h"
+
+std::vector classInstances;
+
+SmackerHandle Smacker_Open(const char* fileName)
+{
+ SmackerHandle newHandle;
+ newHandle.isValid = false;
+ newHandle.instanceIndex = -1;
+
+ SmackerDecoder *newDecoder = new SmackerDecoder();
+ if (!newDecoder->Open(fileName))
+ {
+ delete newDecoder;
+ return newHandle;
+ }
+
+ // add instance to global instance vector
+ classInstances.push_back(newDecoder);
+
+ // get a handle ID
+ newHandle.instanceIndex = classInstances.size() - 1;
+
+ // loaded ok, make handle valid
+ newHandle.isValid = true;
+
+ return newHandle;
+}
+
+void Smacker_Close(SmackerHandle &handle)
+{
+ if (!classInstances.at(handle.instanceIndex))
+ {
+ // invalid handle
+ return;
+ }
+
+ // close bink decoder
+ delete classInstances[handle.instanceIndex];
+ classInstances[handle.instanceIndex] = 0;
+
+ handle.instanceIndex = -1;
+ handle.isValid = false;
+}
+
+uint32_t Smacker_GetNumAudioTracks(SmackerHandle &handle)
+{
+ UNREFERENCED_PARAMETER(handle);
+ // TODO: fixme
+ return 1;
+}
+
+SmackerAudioInfo Smacker_GetAudioTrackDetails(SmackerHandle &handle, uint32_t trackIndex)
+{
+ return classInstances[handle.instanceIndex]->GetAudioTrackDetails(trackIndex);
+}
+
+/* Get a frame's worth of audio data.
+ *
+ * 'data' needs to be a pointer to allocated memory that this function will fill.
+ * You can find the size (in bytes) to make this buffer by calling Bink_GetAudioTrackDetails()
+ * and checking the 'idealBufferSize' member in the returned AudioInfo struct
+ */
+uint32_t Smacker_GetAudioData(SmackerHandle &handle, uint32_t trackIndex, int16_t *data)
+{
+ return classInstances[handle.instanceIndex]->GetAudioData(trackIndex, data);
+}
+
+uint32_t Smacker_GetNumFrames(SmackerHandle &handle)
+{
+ return classInstances[handle.instanceIndex]->GetNumFrames();
+}
+
+void Smacker_GetFrameSize(SmackerHandle &handle, uint32_t &width, uint32_t &height)
+{
+ width = classInstances[handle.instanceIndex]->frameWidth;
+ height = classInstances[handle.instanceIndex]->frameHeight;
+}
+
+uint32_t Smacker_GetCurrentFrameNum(SmackerHandle &handle)
+{
+ return classInstances[handle.instanceIndex]->GetCurrentFrameNum();
+}
+
+uint32_t Smacker_GetNextFrame(SmackerHandle &handle)
+{
+ SmackerDecoder *decoder = classInstances[handle.instanceIndex];
+
+ uint32_t frameIndex = decoder->GetCurrentFrameNum();
+
+ decoder->GetNextFrame();
+
+ return frameIndex;
+}
+
+float Smacker_GetFrameRate(SmackerHandle &handle)
+{
+ return classInstances[handle.instanceIndex]->GetFrameRate();
+}
+
+void Smacker_GetPalette(SmackerHandle &handle, uint8_t *palette)
+{
+ classInstances[handle.instanceIndex]->GetPalette(palette);
+}
+
+void Smacker_GetFrame(SmackerHandle &handle, uint8_t *frame)
+{
+ classInstances[handle.instanceIndex]->GetFrame(frame);
+}
+
+void Smacker_GotoFrame(SmackerHandle &handle, uint32_t frameNum)
+{
+ classInstances[handle.instanceIndex]->GotoFrame(frameNum);
+}
+
+SmackerDecoder::SmackerDecoder()
+{
+ isVer4 = false;
+ currentFrame = 0;
+ picture = 0;
+ nextPos = 0;
+
+ for (int i = 0; i < kMaxAudioTracks; i++)
+ {
+ audioTracks[i].buffer = 0;
+ }
+}
+
+SmackerDecoder::~SmackerDecoder()
+{
+ for (int i = 0; i < kMaxAudioTracks; i++)
+ {
+ delete[] audioTracks[i].buffer;
+ }
+
+ delete[] picture;
+}
+
+// from bswap.h
+static /*av_always_inline av_const*/ uint16_t av_bswap16(uint16_t x)
+{
+ x= (x>>8) | (x<<8);
+ return x;
+}
+
+/* possible runs of blocks */
+static const int block_runs[64] = {
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 58, 59, 128, 256, 512, 1024, 2048 };
+
+enum SmkBlockTypes {
+ SMK_BLK_MONO = 0,
+ SMK_BLK_FULL = 1,
+ SMK_BLK_SKIP = 2,
+ SMK_BLK_FILL = 3 };
+
+/* palette used in Smacker */
+static const uint8_t smk_pal[64] = {
+ 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C,
+ 0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C,
+ 0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D,
+ 0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D,
+ 0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E,
+ 0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE,
+ 0xC3, 0xC7, 0xCB, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF,
+ 0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF
+};
+
+enum SAudFlags {
+ SMK_AUD_PACKED = 0x80000000,
+ SMK_AUD_16BITS = 0x20000000,
+ SMK_AUD_STEREO = 0x10000000,
+ SMK_AUD_BINKAUD = 0x08000000,
+ SMK_AUD_USEDCT = 0x04000000
+};
+
+const int kSMKpal = 0x01;
+const int kFlagRingFrame = 0x01;
+
+const int kTreeBits = 9;
+const int kSMKnode = 0x80000000;
+
+const char *kSMK2iD = "SMK2";
+const char *kSMK4iD = "SMK4";
+
+
+/**
+ * Context used for code reconstructing
+ */
+typedef struct HuffContext {
+ int length;
+ int maxlength;
+ int current;
+
+ std::vector bits;
+ std::vector lengths;
+ std::vector values;
+
+} HuffContext;
+
+/* common parameters used for decode_bigtree */
+typedef struct DBCtx {
+ SmackerCommon::VLCtable v1;
+ SmackerCommon::VLCtable v2;
+ std::vector recode1, recode2;
+ int escapes[3];
+ int *last;
+ int lcur;
+} DBCtx;
+
+
+static void last_reset(std::vector &recode, int *last) {
+ recode[last[0]] = recode[last[1]] = recode[last[2]] = 0;
+}
+
+/* get code and update history */
+int SmackerDecoder::GetCode(SmackerCommon::BitReader &bits, std::vector &recode, int *last)
+{
+ int *table = &recode[0];
+
+ int v, b;
+
+ b = bits.GetPosition();
+
+ while (*table & kSMKnode)
+ {
+ if (bits.GetBit())
+ table += (*table) & (~kSMKnode);
+ table++;
+ }
+ v = *table;
+ b = bits.GetPosition() - b;
+
+ if (v != recode[last[0]]) {
+ recode[last[2]] = recode[last[1]];
+ recode[last[1]] = recode[last[0]];
+ recode[last[0]] = v;
+ }
+ return v;
+}
+
+bool SmackerDecoder::Open(const std::string &fileName)
+{
+ // open the file (read only)
+ file.Open(fileName);
+ if (!file.Is_Open())
+ {
+ SmackerCommon::LogError("Can't open file " + fileName);
+ return false;
+ }
+
+ // check the file signature
+ file.ReadBytes((uint8_t*)signature, 4);
+ if (memcmp(signature, kSMK2iD, 4) != 0
+ && memcmp(signature, kSMK4iD, 4) != 0)
+ {
+ SmackerCommon::LogError("Unknown Smacker signature");
+ return false;
+ }
+
+ if (!memcmp(signature, kSMK4iD, 4)) {
+ isVer4 = true;
+ }
+
+ frameWidth = file.ReadUint32LE();
+ frameHeight = file.ReadUint32LE();
+ nFrames = file.ReadUint32LE();
+
+ picture = new uint8_t[frameWidth * frameHeight];
+
+ int32_t frameRate = file.ReadUint32LE();
+
+ if (frameRate > 0)
+ fps = 1000 / frameRate;
+ else if (frameRate < 0)
+ fps = 100000 / (-frameRate);
+ else
+ fps = 10;
+
+ uint32_t flags = file.ReadUint32LE();
+
+ if (flags & kFlagRingFrame) {
+ nFrames++;
+ }
+
+ for (int i = 0; i < kMaxAudioTracks; i++) {
+ audioTracks[i].sizeInBytes = file.ReadUint32LE();
+ }
+
+ treeSize = file.ReadUint32LE();
+ mMapSize = file.ReadUint32LE();
+ MClrSize = file.ReadUint32LE();
+ fullSize = file.ReadUint32LE();
+ typeSize = file.ReadUint32LE();
+
+ for (int i = 0; i < kMaxAudioTracks; i++) {
+ audioTracks[i].flags = file.ReadUint32LE();
+ }
+
+ // skip pad
+ file.Skip(4);
+
+ if (nFrames > 0xFFFFFF)
+ {
+ SmackerCommon::LogError("Too many frames!");
+ return false;
+ }
+
+ frameSizes.resize(nFrames);
+ frameFlags.resize(nFrames);
+
+ // read frame info
+ for (uint32_t i = 0; i < nFrames; i++) {
+ frameSizes[i] = file.ReadUint32LE();
+ }
+ for (uint32_t i = 0; i < nFrames; i++) {
+ frameFlags[i] = file.ReadByte();
+ }
+
+ // handle possible audio streams
+ for (int i = 0; i < kMaxAudioTracks; i++)
+ {
+ audioTracks[i].buffer = 0;
+ audioTracks[i].bufferSize = 0;
+ audioTracks[i].bytesReadThisFrame = 0;
+
+ if (audioTracks[i].flags & 0xFFFFFF)
+ {
+/*
+ if (audioTracks[i].flags & SMK_AUD_BINKAUD) {
+ audioTracks[i].compressionType = SMK_AUD_BINKAUD;
+ } else if (audioTracks[i].flags & SMK_AUD_USEDCT) {
+ audioTracks[i].compressionType = SMK_AUD_USEDCT;
+ } else if (audioTracks[i].flags & SMK_AUD_PACKED){
+ ast[i]->codec->codec_id = CODEC_ID_SMACKAUDIO;
+ ast[i]->codec->codec_tag = MKTAG('S', 'M', 'K', 'A');
+ } else {
+ ast[i]->codec->codec_id = CODEC_ID_PCM_U8;
+ }
+*/
+ audioTracks[i].nChannels = (audioTracks[i].flags & SMK_AUD_STEREO) ? 2 : 1;
+ audioTracks[i].sampleRate = audioTracks[i].flags & 0xFFFFFF;
+ audioTracks[i].bitsPerSample = (audioTracks[i].flags & SMK_AUD_16BITS) ? 16 : 8;
+ }
+ }
+
+ memset(palette, 0, 768);
+
+ DecodeHeaderTrees();
+
+ // set nextPos to where we are now, as next data is frame 1
+ nextPos = file.GetPosition();
+
+ // determine max buffer sizes for audio tracks
+ file.Seek(nextPos, SmackerCommon::FileStream::kSeekStart);
+
+ uint32_t UNUSED(frameSize) = frameSizes[0] & (~3);
+ uint8_t frameFlag = frameFlags[0];
+
+ // skip over palette
+ if (frameFlag & kSMKpal)
+ {
+ uint32_t size = file.ReadByte();
+ size = size * 4 - 1;
+ file.Skip(size);
+ }
+
+ frameFlag >>= 1;
+
+ for (int i = 0; i < kMaxAudioTracks; i++)
+ {
+ if (frameFlag & 1)
+ {
+ // skip size
+ file.Skip(4);
+
+ uint32_t unpackedSize = file.ReadUint32LE();
+
+ audioTracks[i].bufferSize = unpackedSize;
+ audioTracks[i].buffer = new uint8_t[unpackedSize];
+ }
+ frameFlag >>= 1;
+ }
+
+ return true;
+}
+
+// static int smacker_decode_tree(GetBitContext *gb, HuffContext *hc, uint32_t prefix, int length)
+int SmackerDecoder::DecodeTree(SmackerCommon::BitReader &bits, HuffContext *hc, uint32_t prefix, int length)
+{
+ if (!bits.GetBit()) // Leaf
+ {
+ if (hc->current >= 256){
+ SmackerCommon::LogError("Tree size exceeded!");
+ return -1;
+ }
+ if (length){
+ hc->bits[hc->current] = prefix;
+ hc->lengths[hc->current] = length;
+ } else {
+ hc->bits[hc->current] = 0;
+ hc->lengths[hc->current] = 0;
+ }
+ hc->values[hc->current] = bits.GetBits(8);
+
+ hc->current++;
+ if (hc->maxlength < length)
+ hc->maxlength = length;
+ return 0;
+ } else { //Node
+ int r;
+ length++;
+ r = DecodeTree(bits, hc, prefix, length);
+ if (r)
+ return r;
+ return DecodeTree(bits, hc, prefix | (1 << (length - 1)), length);
+ }
+}
+
+/**
+ * Decode header tree
+ */
+int SmackerDecoder::DecodeBigTree(SmackerCommon::BitReader &bits, HuffContext *hc, DBCtx *ctx)
+{
+ if (!bits.GetBit()) // Leaf
+ {
+ int val, i1, i2, b1, b2;
+
+ i1 = 0;
+ i2 = 0;
+
+ if (hc->current >= hc->length){
+ SmackerCommon::LogError("Tree size exceeded!");
+ return -1;
+ }
+
+ b1 = bits.GetPosition();
+
+ if (VLC_GetSize(ctx->v1))
+ {
+ i1 = VLC_GetCodeBits(bits, ctx->v1);
+ }
+
+ b1 = bits.GetPosition() - b1;
+ b2 = bits.GetPosition();
+
+ if (VLC_GetSize(ctx->v2))
+ {
+ i2 = VLC_GetCodeBits(bits, ctx->v2);
+ }
+
+ b2 = bits.GetPosition() - b2;
+ if (i1 < 0 || i2 < 0)
+ return -1;
+ val = ctx->recode1[i1] | (ctx->recode2[i2] << 8);
+ if (val == ctx->escapes[0]) {
+ ctx->last[0] = hc->current;
+ val = 0;
+ } else if (val == ctx->escapes[1]) {
+ ctx->last[1] = hc->current;
+ val = 0;
+ } else if (val == ctx->escapes[2]) {
+ ctx->last[2] = hc->current;
+ val = 0;
+ }
+
+ hc->values[hc->current++] = val;
+ return 1;
+ } else { //Node
+ int r = 0, t;
+
+ t = hc->current++;
+ r = DecodeBigTree(bits, hc, ctx);
+ if (r < 0)
+ return r;
+ hc->values[t] = kSMKnode | r;
+ r++;
+ r += DecodeBigTree(bits, hc, ctx);
+ return r;
+ }
+}
+
+/**
+ * Store large tree as Libav's vlc codes
+ */
+int SmackerDecoder::DecodeHeaderTree(SmackerCommon::BitReader &bits, std::vector &recodes, int *last, int size)
+{
+ HuffContext huff;
+ HuffContext tmp1, tmp2;
+ int escapes[3];
+ DBCtx ctx;
+
+ if ((uint32_t)size >= UINT_MAX>>4)
+ {
+ SmackerCommon::LogError("Size too large");
+ return -1;
+ }
+
+ tmp1.length = 256;
+ tmp1.maxlength = 0;
+ tmp1.current = 0;
+
+ tmp1.bits.resize(256);
+ tmp1.lengths.resize(256);
+ tmp1.values.resize(256);
+
+ tmp2.length = 256;
+ tmp2.maxlength = 0;
+ tmp2.current = 0;
+
+ tmp2.bits.resize(256);
+ tmp2.lengths.resize(256);
+ tmp2.values.resize(256);
+
+ // low byte tree
+ if (bits.GetBit()) // 1: Read Tag
+ {
+ DecodeTree(bits, &tmp1, 0, 0);
+
+ bits.SkipBits(1);
+
+ VLC_InitTable(ctx.v1, tmp1.maxlength, tmp1.current, &tmp1.lengths[0], &tmp1.bits[0]);
+ }
+ else
+ {
+ // Skipping low bytes tree
+ }
+
+ // high byte tree
+ if (bits.GetBit())
+ {
+ DecodeTree(bits, &tmp2, 0, 0);
+
+ uint32_t UNUSED(end) = bits.GetPosition();
+
+ bits.SkipBits(1);
+
+ VLC_InitTable(ctx.v2, tmp2.maxlength, tmp2.current, &tmp2.lengths[0], &tmp2.bits[0]);
+ }
+ else
+ {
+ // Skipping high bytes tree
+ }
+
+ escapes[0] = bits.GetBits(8);
+ escapes[0] |= bits.GetBits(8) << 8;
+ escapes[1] = bits.GetBits(8);
+ escapes[1] |= bits.GetBits(8) << 8;
+ escapes[2] = bits.GetBits(8);
+ escapes[2] |= bits.GetBits(8) << 8;
+
+ last[0] = last[1] = last[2] = -1;
+
+ ctx.escapes[0] = escapes[0];
+ ctx.escapes[1] = escapes[1];
+ ctx.escapes[2] = escapes[2];
+
+ ctx.recode1 = tmp1.values;
+ ctx.recode2 = tmp2.values;
+ ctx.last = last;
+
+ huff.length = ((size + 3) >> 2) + 3;
+ huff.maxlength = 0;
+ huff.current = 0;
+ huff.values.resize(huff.length);
+
+ DecodeBigTree(bits, &huff, &ctx);
+
+ bits.SkipBits(1);
+
+ if (ctx.last[0] == -1) ctx.last[0] = huff.current++;
+ if (ctx.last[1] == -1) ctx.last[1] = huff.current++;
+ if (ctx.last[2] == -1) ctx.last[2] = huff.current++;
+
+ recodes = huff.values;
+
+ return 0;
+}
+
+// static int decode_header_trees(SmackVContext *smk) {
+bool SmackerDecoder::DecodeHeaderTrees()
+{
+ SmackerCommon::BitReader bits(file, treeSize);
+
+ if (!bits.GetBit())
+ {
+ // Skipping MMAP tree
+ mmap_tbl.resize(2);
+ mmap_tbl[0] = 0;
+ mmap_last[0] = mmap_last[1] = mmap_last[2] = 1;
+ }
+ else
+ {
+ DecodeHeaderTree(bits, mmap_tbl, mmap_last, mMapSize);
+ }
+
+ if (!bits.GetBit())
+ {
+ // Skipping MCLR tree
+ mclr_tbl.resize(2);
+ mclr_tbl[0] = 0;
+ mclr_last[0] = mclr_last[1] = mclr_last[2] = 1;
+ }
+ else
+ {
+ DecodeHeaderTree(bits, mclr_tbl, mclr_last, MClrSize);
+ }
+
+ if (!bits.GetBit())
+ {
+ // Skipping FULL tree
+ full_tbl.resize(2);
+ full_tbl[0] = 0;
+ full_last[0] = full_last[1] = full_last[2] = 1;
+ }
+ else
+ {
+ DecodeHeaderTree(bits, full_tbl, full_last, fullSize);
+ }
+
+ if (!bits.GetBit())
+ {
+ // Skipping TYPE tree
+ type_tbl.resize(2);
+ type_tbl[0] = 0;
+ type_last[0] = type_last[1] = type_last[2] = 1;
+ }
+ else
+ {
+ DecodeHeaderTree(bits, type_tbl, type_last, typeSize);
+ }
+
+ /* FIXME - we don't seems to read/use EVERY bit we 'load' into the bit reader
+ * and as my bitreader reads from the file rather than a buffer read from file
+ * of size 'treeSize', I need to make sure I consume the remaining bits (and thus increment
+ * the file read position to where the code expects it to be when this function returns (ie
+ * 'treeSize' number of bytes must be read
+ */
+ uint32_t left = bits.GetSize() - bits.GetPosition();
+ bits.SkipBits(left);
+
+ return true;
+}
+
+void SmackerDecoder::GetNextFrame()
+{
+ ReadPacket();
+}
+
+int SmackerDecoder::ReadPacket()
+{
+ // test-remove
+ if (currentFrame >= nFrames)
+ return 1;
+
+ // seek to next frame position
+ file.Seek(nextPos, SmackerCommon::FileStream::kSeekStart);
+
+ uint32_t frameSize = frameSizes[currentFrame] & (~3);
+ uint8_t frameFlag = frameFlags[currentFrame];
+
+ // handle palette change
+ if (frameFlag & kSMKpal)
+ {
+ int size, sz, t, off, j, pos;
+ uint8_t *pal = palette;
+ uint8_t oldpal[768];
+
+ memcpy(oldpal, pal, 768);
+ size = file.ReadByte();
+ size = size * 4 - 1;
+ frameSize -= size;
+ frameSize--;
+ sz = 0;
+ pos = file.GetPosition() + size;
+
+ while (sz < 256)
+ {
+ t = file.ReadByte();
+ if (t & 0x80){ /* skip palette entries */
+ sz += (t & 0x7F) + 1;
+ pal += ((t & 0x7F) + 1) * 3;
+ } else if (t & 0x40){ /* copy with offset */
+ off = file.ReadByte() * 3;
+ j = (t & 0x3F) + 1;
+ while (j-- && sz < 256) {
+ *pal++ = oldpal[off + 0];
+ *pal++ = oldpal[off + 1];
+ *pal++ = oldpal[off + 2];
+ sz++;
+ off += 3;
+ }
+ } else { /* new entries */
+ *pal++ = smk_pal[t];
+ *pal++ = smk_pal[file.ReadByte() & 0x3F];
+ *pal++ = smk_pal[file.ReadByte() & 0x3F];
+ sz++;
+ }
+ }
+
+ file.Seek(pos, SmackerCommon::FileStream::kSeekStart);
+ }
+
+ frameFlag >>= 1;
+
+ // check for and handle audio
+ for (int i = 0; i < kMaxAudioTracks; i++)
+ {
+ audioTracks[i].bytesReadThisFrame = 0;
+
+ if (frameFlag & 1)
+ {
+ uint32_t size = file.ReadUint32LE() - 4;
+ frameSize -= size;
+ frameSize -= 4;
+
+ DecodeAudio(size, audioTracks[i]);
+ }
+ frameFlag >>= 1;
+ }
+
+ if (frameSize == 0) {
+ return -1;
+ }
+
+ DecodeFrame(frameSize);
+
+ currentFrame++;
+
+ nextPos = file.GetPosition();
+
+ return 0;
+}
+
+int SmackerDecoder::DecodeFrame(uint32_t frameSize)
+{
+ last_reset(mmap_tbl, mmap_last);
+ last_reset(mclr_tbl, mclr_last);
+ last_reset(full_tbl, full_last);
+ last_reset(type_tbl, type_last);
+
+ int blocks, blk, bw, bh;
+ int i;
+ int stride;
+
+ uint8_t *out = picture; // set to output image
+
+ blk = 0;
+ bw = frameWidth >> 2;
+ bh = frameHeight >> 2;
+ blocks = bw * bh;
+
+ stride = frameWidth;
+
+ uint32_t UNUSED(fileStart) = file.GetPosition();
+
+ SmackerCommon::BitReader bits(file, frameSize);
+
+ while (blk < blocks)
+ {
+ int type, run, mode;
+ uint16_t pix;
+
+ type = GetCode(bits, type_tbl, type_last);
+ run = block_runs[(type >> 2) & 0x3F];
+ switch (type & 3)
+ {
+ case SMK_BLK_MONO:
+ while (run-- && blk < blocks)
+ {
+ int clr, map;
+ int hi, lo;
+ clr = GetCode(bits, mclr_tbl, mclr_last);
+ map = GetCode(bits, mmap_tbl, mmap_last);
+
+ out = picture + (blk / bw) * (stride * 4) + (blk % bw) * 4;
+
+ hi = clr >> 8;
+ lo = clr & 0xFF;
+ for (i = 0; i < 4; i++)
+ {
+ if (map & 1) out[0] = hi; else out[0] = lo;
+ if (map & 2) out[1] = hi; else out[1] = lo;
+ if (map & 4) out[2] = hi; else out[2] = lo;
+ if (map & 8) out[3] = hi; else out[3] = lo;
+ map >>= 4;
+ out += stride;
+ }
+ blk++;
+ }
+ break;
+ case SMK_BLK_FULL:
+ mode = 0;
+ if (kSMK4iD == signature) // In case of Smacker v4 we have three modes
+ {
+ if (bits.GetBit()) mode = 1;
+ else if (bits.GetBit()) mode = 2;
+ }
+
+ while (run-- && blk < blocks)
+ {
+ out = picture + (blk / bw) * (stride * 4) + (blk % bw) * 4;
+ switch (mode)
+ {
+ case 0:
+ for (i = 0; i < 4; i++)
+ {
+ pix = GetCode(bits, full_tbl, full_last);
+// FIX AV_WL16(out+2, pix);
+ out[2] = pix & 0xff;
+ out[3] = pix >> 8;
+
+ pix = GetCode(bits, full_tbl, full_last);
+// FIX AV_WL16(out, pix);
+ out[0] = pix & 0xff;
+ out[1] = pix >> 8;
+ out += stride;
+ }
+ break;
+ case 1:
+ pix = GetCode(bits, full_tbl, full_last);
+ out[0] = out[1] = pix & 0xFF;
+ out[2] = out[3] = pix >> 8;
+ out += stride;
+ out[0] = out[1] = pix & 0xFF;
+ out[2] = out[3] = pix >> 8;
+ out += stride;
+ pix = GetCode(bits, full_tbl, full_last);
+ out[0] = out[1] = pix & 0xFF;
+ out[2] = out[3] = pix >> 8;
+ out += stride;
+ out[0] = out[1] = pix & 0xFF;
+ out[2] = out[3] = pix >> 8;
+ out += stride;
+ break;
+ case 2:
+ for (i = 0; i < 2; i++)
+ {
+ uint16_t pix1, pix2;
+ pix2 = GetCode(bits, full_tbl, full_last);
+ pix1 = GetCode(bits, full_tbl, full_last);
+
+// FIX AV_WL16(out, pix1);
+// FIX AV_WL16(out+2, pix2);
+ out[0] = pix1 & 0xff;
+ out[1] = pix1 >> 8;
+ out[2] = pix2 & 0xff;
+ out[3] = pix2 >> 8;
+
+ out += stride;
+
+// FIX AV_WL16(out, pix1);
+// FIX AV_WL16(out+2, pix2);
+ out[0] = pix1 & 0xff;
+ out[1] = pix1 >> 8;
+ out[2] = pix2 & 0xff;
+ out[3] = pix2 >> 8;
+
+ out += stride;
+ }
+ break;
+ }
+ blk++;
+ }
+ break;
+ case SMK_BLK_SKIP:
+ while (run-- && blk < blocks)
+ blk++;
+ break;
+ case SMK_BLK_FILL:
+ mode = type >> 8;
+ while (run-- && blk < blocks)
+ {
+ uint32_t col;
+ out = picture + (blk / bw) * (stride * 4) + (blk % bw) * 4;
+ col = mode * 0x01010101;
+ for (i = 0; i < 4; i++) {
+ *((uint32_t*)out) = col;
+ out += stride;
+ }
+ blk++;
+ }
+ break;
+ }
+ }
+
+ /* FIXME - we don't seems to read/use EVERY bit we 'load' into the bit reader
+ * and as my bitreader reads from the file rather than a buffer read from file
+ * of size 'frameSize', I need to make sure I consume the remaining bits (and thus increment
+ * the file read position to where the code expects it to be when this function returns (ie
+ * 'frameSize' number of bytes must be read
+ */
+ uint32_t left = bits.GetSize() - bits.GetPosition();
+ bits.SkipBits(left);
+
+ return 0;
+}
+
+/**
+ * Decode Smacker audio data
+ */
+int SmackerDecoder::DecodeAudio(uint32_t size, SmackerAudioTrack &track)
+{
+ HuffContext h[4];
+ SmackerCommon::VLCtable vlc[4];
+ int val;
+ int i, res;
+ int unpackedSize;
+ int sampleBits, stereo;
+ int pred[2] = {0, 0};
+
+ int16_t *samples = reinterpret_cast(track.buffer);
+ int8_t *samples8 = reinterpret_cast(track.buffer);
+
+ int buf_size = track.bufferSize;
+
+ if (buf_size <= 4) {
+ SmackerCommon::LogError("packet is too small");
+ return -1;
+ }
+
+ SmackerCommon::BitReader bits(file, size);
+
+ unpackedSize = bits.GetBits(32);
+
+ if (!bits.GetBit()) {
+ // no sound data
+ return 1;
+ }
+
+ stereo = bits.GetBit();
+ sampleBits = bits.GetBit();
+
+ if (stereo ^ (track.nChannels != 1)) {
+ SmackerCommon::LogError("channels mismatch");
+ return -1;
+ }
+
+ memset(h, 0, sizeof(HuffContext) * 4);
+
+ // Initialize
+ for (i = 0; i < (1 << (sampleBits + stereo)); i++) {
+ h[i].length = 256;
+ h[i].maxlength = 0;
+ h[i].current = 0;
+ h[i].bits.resize(256);
+ h[i].lengths.resize(256);
+ h[i].values.resize(256);
+
+ bits.SkipBits(1);
+ DecodeTree(bits, &h[i], 0, 0);
+ bits.SkipBits(1);
+
+ if (h[i].current > 1) {
+ VLC_InitTable(vlc[i], h[i].maxlength, h[i].current, &h[i].lengths[0], &h[i].bits[0]);
+ }
+ }
+ if (sampleBits) { //decode 16-bit data
+ for (i = stereo; i >= 0; i--)
+ pred[i] = av_bswap16(bits.GetBits(16));
+ for (i = 0; i <= stereo; i++)
+ *samples++ = pred[i];
+ for (; i < unpackedSize / 2; i++) {
+ if (i & stereo) {
+ if (VLC_GetSize(vlc[2]))
+ res = VLC_GetCodeBits(bits, vlc[2]);
+ else
+ res = 0;
+ val = h[2].values[res];
+ if (VLC_GetSize(vlc[3]))
+ res = VLC_GetCodeBits(bits, vlc[3]);
+ else
+ res = 0;
+ val |= h[3].values[res] << 8;
+ pred[1] += (int16_t)val;
+ *samples++ = pred[1];
+ } else {
+ if (VLC_GetSize(vlc[0]))
+ res = VLC_GetCodeBits(bits, vlc[0]);
+ else
+ res = 0;
+ val = h[0].values[res];
+ if (VLC_GetSize(vlc[1]))
+ res = VLC_GetCodeBits(bits, vlc[1]);
+ else
+ res = 0;
+ val |= h[1].values[res] << 8;
+ pred[0] += val;
+ *samples++ = pred[0];
+ }
+ }
+ }
+ else { //8-bit data
+ for (i = stereo; i >= 0; i--)
+ pred[i] = bits.GetBits(8);
+ for (i = 0; i <= stereo; i++)
+ *samples8++ = pred[i];
+ for (; i < unpackedSize; i++) {
+ if (i & stereo){
+ if (VLC_GetSize(vlc[1]))
+ res = VLC_GetCodeBits(bits, vlc[1]);
+ else
+ res = 0;
+ pred[1] += (int8_t)h[1].values[res];
+ *samples8++ = pred[1];
+ } else {
+ if (VLC_GetSize(vlc[0]))
+ res = VLC_GetCodeBits(bits, vlc[0]);
+ else
+ res = 0;
+ pred[0] += (int8_t)h[0].values[res];
+ *samples8++ = pred[0];
+ }
+ }
+ }
+
+ track.bytesReadThisFrame = unpackedSize;
+
+ uint32_t left = bits.GetSize() - bits.GetPosition();
+ bits.SkipBits(left);
+
+ return 0;
+}
+
+void SmackerDecoder::GetPalette(uint8_t *palette)
+{
+ memcpy(palette, this->palette, 768);
+}
+
+void SmackerDecoder::GetFrame(uint8_t *frame)
+{
+ memcpy(frame, this->picture, frameWidth * frameHeight);
+}
+
+void SmackerDecoder::GetFrameSize(uint32_t &width, uint32_t &height)
+{
+ width = this->frameWidth;
+ height = this->frameHeight;
+}
+
+uint32_t SmackerDecoder::GetNumFrames()
+{
+ return nFrames;
+}
+
+uint32_t SmackerDecoder::GetCurrentFrameNum()
+{
+ return currentFrame;
+}
+
+float SmackerDecoder::GetFrameRate()
+{
+ return (float)fps;
+}
+
+void SmackerDecoder::GotoFrame(uint32_t frameNum)
+{
+ if (frameNum >= nFrames) {
+ SmackerCommon::LogError("Invalid frame number for GotoFrame");
+ return;
+ }
+
+
+ // TODO
+
+ // seek to the desired frame (just set currentFrame)
+// currentFrame = frameNum;
+
+ // what else? (memset some stuff?)
+}
+
+
+SmackerAudioInfo SmackerDecoder::GetAudioTrackDetails(uint32_t trackIndex)
+{
+ SmackerAudioInfo info;
+ SmackerAudioTrack *track = &audioTracks[trackIndex];
+
+ info.sampleRate = track->sampleRate;
+ info.nChannels = track->nChannels;
+ info.bitsPerSample = track->bitsPerSample;
+
+ // audio buffer size in bytes
+ info.idealBufferSize = track->bufferSize;
+
+ return info;
+}
+
+uint32_t SmackerDecoder::GetAudioData(uint32_t trackIndex, int16_t *audioBuffer)
+{
+ if (!audioBuffer) {
+ return 0;
+ }
+
+ SmackerAudioTrack *track = &audioTracks[trackIndex];
+
+ if (track->bytesReadThisFrame) {
+ memcpy(audioBuffer, track->buffer, std::min(track->bufferSize, track->bytesReadThisFrame));
+ }
+
+ return track->bytesReadThisFrame;
+}
diff --git a/source/rr/src/config.cpp b/source/rr/src/config.cpp
index 70029239e..a802521fc 100644
--- a/source/rr/src/config.cpp
+++ b/source/rr/src/config.cpp
@@ -599,8 +599,20 @@ int32_t CONFIG_ReadSetup(void)
if (ud.config.scripthandle < 0)
{
- ud.config.scripthandle = SCRIPT_Load(g_setupFileName);
- }
+ if (buildvfs_exists(g_setupFileName)) // JBF 20031211
+ ud.config.scripthandle = SCRIPT_Load(g_setupFileName);
+#if !defined(EDUKE32_TOUCH_DEVICES) && !defined(EDUKE32_STANDALONE)
+ else if (buildvfs_exists(SETUPFILENAME))
+ {
+ int const i = wm_ynbox("Import Configuration Settings",
+ "The configuration file \"%s\" was not found. "
+ "Import configuration data from \"%s\"?",
+ g_setupFileName, SETUPFILENAME);
+ if (i)
+ ud.config.scripthandle = SCRIPT_Load(SETUPFILENAME);
+ }
+#endif
+ }
pathsearchmode = 0;
diff --git a/source/rr/src/duke3d.h b/source/rr/src/duke3d.h
index 0afe11139..b3e3bf6d1 100644
--- a/source/rr/src/duke3d.h
+++ b/source/rr/src/duke3d.h
@@ -39,7 +39,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "cache1d.h"
#include "pragmas.h"
#include "baselayer.h"
-#include "file_lib.h"
#include "keyboard.h"
#include "fx_man.h"
diff --git a/source/rr/src/file_lib.cpp b/source/rr/src/file_lib.cpp
deleted file mode 100644
index a8a253857..000000000
--- a/source/rr/src/file_lib.cpp
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * file_lib.c
- * File functions to emulate MACT
- *
- * by Jonathon Fowler
- *
- * Since we weren't given the source for MACT386.LIB so I've had to do some
- * creative interpolation here.
- *
- */
-//-------------------------------------------------------------------------
-/*
-Duke Nukem Copyright (C) 1996, 2003 3D Realms Entertainment
-
-This file is part of Duke Nukem 3D version 1.5 - Atomic Edition
-
-Duke Nukem 3D is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-*/
-//-------------------------------------------------------------------------
-
-#include "compat.h"
-
-#include "file_lib.h"
-#include "cache1d.h"
-#include "baselayer.h"
-
-#define MaxFiles 20
-static char *FileNames[MaxFiles];
-
-int32_t SafeOpen(const char *filename, int32_t mode, int32_t sharemode)
-{
- int32_t h;
-
- h = openfrompath(filename, mode, sharemode);
- if (h < 0)
- {
- initprintf("Error opening %s: %s", filename, strerror(errno));
- return h;
- }
-
- if (h < MaxFiles)
- {
- Bfree(FileNames[h]);
- FileNames[h] = (char*)Xmalloc(strlen(filename)+1);
- strcpy(FileNames[h], filename);
- }
-
- return h;
-}
-
-int32_t SafeOpenRead(const char *filename, int32_t filetype)
-{
- switch (filetype)
- {
- case filetype_binary:
- return SafeOpen(filename, O_RDONLY|O_BINARY, BS_IREAD);
- case filetype_text:
- return SafeOpen(filename, O_RDONLY|O_TEXT, BS_IREAD);
- default:
- initprintf("SafeOpenRead: Illegal filetype specified");
- return -1;
- }
-}
-
-void SafeClose(int32_t handle)
-{
- if (handle < 0) return;
- if (close(handle) < 0)
- {
- if (handle < MaxFiles)
- initprintf("Unable to close file %s", FileNames[handle]);
- else
- initprintf("Unable to close file");
- return;
- }
-
- if (handle < MaxFiles && FileNames[handle])
- {
- DO_FREE_AND_NULL(FileNames[handle]);
- }
-}
-
-int32_t SafeFileExists(const char *filename)
-{
- if (!access(filename, F_OK)) return TRUE;
- return FALSE;
-}
-
-int32_t SafeFileLength(int32_t handle)
-{
- if (handle < 0) return -1;
- return _filelength(handle);
-}
-
-void SafeRead(int32_t handle, void *buffer, int32_t count)
-{
- int32_t b;
-
- b = read(handle, buffer, count);
- if (b != count)
- {
- close(handle);
- if (handle < MaxFiles)
- initprintf("File read failure %s reading %d bytes from file %s.",
- strerror(errno), count, FileNames[handle]);
- else
- initprintf("File read failure %s reading %d bytes.",
- strerror(errno), count);
- return;
- }
-}
-
-
diff --git a/source/rr/src/file_lib.h b/source/rr/src/file_lib.h
deleted file mode 100644
index e99911497..000000000
--- a/source/rr/src/file_lib.h
+++ /dev/null
@@ -1,262 +0,0 @@
-//-------------------------------------------------------------------------
-/*
-Copyright (C) 1996, 2003 - 3D Realms Entertainment
-
-This file is part of Duke Nukem 3D version 1.5 - Atomic Edition
-
-Duke Nukem 3D is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
-Original Source: 1996 - Todd Replogle
-Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms
-*/
-//-------------------------------------------------------------------------
-
-#pragma once
-
-#ifndef file_lib_public_
-#define file_lib_public_
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-enum
- {
- filetype_binary,
- filetype_text
- };
-
-enum
- {
- access_read,
- access_write,
- access_append
- };
-
-//==========================================================================
-//
-// SafeOpenWrite - Opens a file for writing, returns handle
-//
-//==========================================================================
-int32_t SafeOpenWrite ( const char * filename, int32_t filetype );
-
-//==========================================================================
-//
-// SafeOpenRead - Opens a file for reading, returns handle
-//
-//==========================================================================
-int32_t SafeOpenRead ( const char * filename, int32_t filetype );
-
-//==========================================================================
-//
-// SafeOpenAppend - Opens a file for appending, returns handle
-//
-//==========================================================================
-int32_t SafeOpenAppend ( const char * filename, int32_t filetype );
-
-//==========================================================================
-//
-// SafeClose - Close a file denoted by the file handle
-//
-//==========================================================================
-void SafeClose ( int32_t handle );
-
-//==========================================================================
-//
-// SafeFileExists - Checks for existence of file
-//
-//==========================================================================
-int32_t SafeFileExists ( const char * filename );
-
-//==========================================================================
-//
-// SafeFileLength - Get length of a file pointed to by handle
-//
-//==========================================================================
-int32_t SafeFileLength ( int32_t handle );
-
-//==========================================================================
-//
-// SafeRead - reads from a handle
-//
-// handle - handle of file to read from
-//
-// buffer - pointer of buffer to read into
-//
-// count - number of bytes to read
-//
-//==========================================================================
-void SafeRead (int32_t handle, void *buffer, int32_t count);
-
-//==========================================================================
-//
-// SafeWrite - writes to a handle
-//
-// handle - handle of file to write to
-//
-// buffer - pointer of buffer to write from
-//
-// count - number of bytes to write
-//
-//==========================================================================
-void SafeWrite (int32_t handle, void *buffer, int32_t count);
-
-//==========================================================================
-//
-// LoadFile - Load a file
-//
-// filename - name of file
-//
-// bufferptr - pointer to pointer of buffer to read into
-//
-// returns number of bytes read
-//
-//==========================================================================
-int32_t LoadFile ( const char * filename, void ** bufferptr );
-
-//==========================================================================
-//
-// SaveFile - Save a file
-//
-// filename - name of file
-//
-// bufferptr - pointer to buffer to write from
-//
-// count - number of bytes to write
-//
-//==========================================================================
-void SaveFile ( const char * filename, void * bufferptr, int32_t count );
-
-//==========================================================================
-//
-// GetPathFromEnvironment - Add a pathname described in an environment
-// variable to a standard filename.
-//
-// fullname - final string containing entire path
-//
-// envname - string naming enivronment variable
-//
-// filename - standard filename
-//
-//==========================================================================
-void GetPathFromEnvironment( char *fullname, const char *envname, const char *filename );
-
-//==========================================================================
-//
-// DefaultExtension - Add a default extension to a path
-//
-// path - a path
-//
-// extension - default extension should include '.'
-//
-//==========================================================================
-void DefaultExtension (char *path, const char *extension);
-
-//==========================================================================
-//
-// DefaultPath - Add the default path to a filename if it doesn't have one
-//
-// path - filename
-//
-// extension - default path
-//
-//==========================================================================
-void DefaultPath (char *path, const char *basepath);
-
-//==========================================================================
-//
-// ExtractFileBase - Extract the base filename from a path
-//
-// path - the path
-//
-// dest - where the file base name will be placed
-//
-//==========================================================================
-void ExtractFileBase (char *path, char *dest);
-
-//==========================================================================
-//
-// GetExtension - Extract the extension from a name
-// returns true if an extension is found
-// returns false otherwise
-//
-//==========================================================================
-int32_t GetExtension( char *filename, char *extension );
-
-//==========================================================================
-//
-// SetExtension - Sets the extension from a name. Assumes that enough
-// space is left at the end of the string to hold an extension.
-//
-//==========================================================================
-void SetExtension( char *filename, const char *extension );
-
-#ifdef __MSDOS__
-//******************************************************************************
-//
-// GetPath
-//
-// Purpose
-// To parse the directory entered by the user to make the directory.
-//
-// Parms
-// Path - the path to be parsed.
-//
-// Returns
-// Pointer to next path
-//
-//******************************************************************************
-char * GetPath (char * path, char *dir);
-
-//******************************************************************************
-//
-// ChangeDirectory ()
-//
-// Purpose
-// To change to a directory. Checks for drive changes.
-//
-// Parms
-// path - The path to change to.
-//
-// Returns
-// TRUE - If successful.
-// FALSE - If unsuccessful.
-//
-//******************************************************************************
-int32_t ChangeDirectory (char * path);
-
-//******************************************************************************
-//
-// ChangeDrive ()
-//
-// Purpose
-// To change drives.
-//
-// Parms
-// drive - The drive to change to.
-//
-// Returns
-// TRUE - If drive change successful.
-// FALSE - If drive change unsuccessful.
-//
-//******************************************************************************
-int32_t ChangeDrive (char *drive);
-
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-#endif