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