diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 4388487a5..25c206db5 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -203,21 +203,21 @@ Note: All fields default to false unless mentioned otherwise. nogradient_top = ; // disables color gradient on upper tier. (Hardware rendering only.) flipgradient_top = ; // flips gradient colors on upper tier. (Hardware rendering only.) clampgradient_top = ; // clamps gradient on upper tier to actual bounds (default is the entire front sector height, hardware rendering only.) - useowncolors_top = ; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector. + useowncolors_top = ; // Set to 1 to use the colors set in the sidedef. Default is true if uppercolor or lowercolor are set, otherwise it's false uppercolor_top = ; // Material color of the top of the upper tier. lowercolor_top = ; // Material color of the bottom of the upper tier. (Hardware rendering only.) nogradient_mid = ; // disables color gradient on middle tier. (Hardware rendering only.) flipgradient_mid = ; // flips gradient colors on middle tier. (Hardware rendering only.) clampgradient_mid = ; // clamps gradient on middle tier to actual bounds (default is the entire front sector height, hardware rendering only.) - useowncolors_mid = ; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector. + useowncolors_mid = ; // Set to 1 to use the colors set in the sidedef. Default is true if uppercolor or lowercolor are set, otherwise it's false uppercolor_mid = ; // Material color of the top of the middle tier. lowercolor_mid = ; // Material color of the bottom of the middle tier. (Hardware rendering only.) nogradient_bottom = ; // disables color gradient on lower tier. (Hardware rendering only.) flipgradient_bottom = ; // flips gradient colors on lower tier. (Hardware rendering only.) clampgradient_bottom = ;// clamps gradient on lower tier to actual bounds (default is the entire front sector height, hardware rendering only.) - useowncolors_bottom = ; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector. + useowncolors_bottom = ; // Set to 1 to use the colors set in the sidedef. Default is true if uppercolor or lowercolor are set, otherwise it's false uppercolor_bottom = ; // Material color of the top of the lower tier. lowercolor_bottom = ; // Material color of the bottom of the lower tier. (Hardware rendering only.) @@ -307,6 +307,8 @@ Note: All fields default to false unless mentioned otherwise. leakiness = ; // Probability of leaking through radiation suit (0 = never, 256 = always), default = 0. damageterraineffect = ; // Will spawn a terrain splash when damage is inflicted. Default = false. damagehazard = ; // Changes damage model to Strife's delayed damage for the given sector. Default = false. + hurtmonsters = ; // Non-players like monsters and decorations are hurt by this sector in the same manner as player. Doesn't work with damagehazard. + harminair = ; // Actors in this sector are harmed by the damage effects of the floor even if they aren't touching it. floorterrain = ; // Sets the terrain for the sector's floor. Default = 'use the flat texture's terrain definition.' ceilingterrain = ; // Sets the terrain for the sector's ceiling. Default = 'use the flat texture's terrain definition.' floor_reflect = ; // reflectiveness of floor (OpenGL only, not functional on sloped sectors) diff --git a/src/common/2d/v_2ddrawer.h b/src/common/2d/v_2ddrawer.h index 1445a168f..413c2e1d6 100644 --- a/src/common/2d/v_2ddrawer.h +++ b/src/common/2d/v_2ddrawer.h @@ -8,6 +8,7 @@ #include "renderstyle.h" #include "dobject.h" #include "refcounted.h" +#include "printf.h" struct DrawParms; struct FColormap; @@ -281,6 +282,7 @@ public: class FCanvas : public DObject { DECLARE_CLASS(FCanvas, DObject) + void OnDestroy() override { I_Error("Calling Destroy on a canvas object is not allowed."); } public: F2DDrawer Drawer; FCanvasTexture* Tex = nullptr; diff --git a/src/common/2d/wipe.cpp b/src/common/2d/wipe.cpp index f359e3d18..3682070db 100644 --- a/src/common/2d/wipe.cpp +++ b/src/common/2d/wipe.cpp @@ -43,6 +43,8 @@ #include "s_soundinternal.h" #include "i_time.h" +EXTERN_CVAR(Bool, cl_capfps) + class FBurnTexture : public FTexture { TArray WorkBuffer; @@ -163,6 +165,8 @@ protected: public: virtual ~Wiper(); virtual bool Run(int ticks) = 0; + virtual bool RunInterpolated(double ticks) { return true; }; + virtual bool Interpolatable() { return false; } virtual void SetTextures(FGameTexture* startscreen, FGameTexture* endscreen) { startScreen = startscreen; @@ -177,9 +181,11 @@ class Wiper_Crossfade : public Wiper { public: bool Run(int ticks) override; + bool RunInterpolated(double ticks) override; + bool Interpolatable() override { return true; } private: - int Clock = 0; + float Clock = 0; }; class Wiper_Melt : public Wiper @@ -187,10 +193,12 @@ class Wiper_Melt : public Wiper public: Wiper_Melt(); bool Run(int ticks) override; + bool RunInterpolated(double ticks) override; + bool Interpolatable() override { return true; } private: enum { WIDTH = 320, HEIGHT = 200 }; - int y[WIDTH]; + double y[WIDTH]; }; class Wiper_Burn : public Wiper @@ -286,7 +294,23 @@ bool Wiper_Crossfade::Run(int ticks) Clock += ticks; DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE); DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE); - return Clock >= 32; + return Clock >= 32.; +} + +//========================================================================== +// +// OpenGLFrameBuffer :: Wiper_Crossfade :: Run +// +// Fades the old screen into the new one over 32 ticks. +// +//========================================================================== + +bool Wiper_Crossfade::RunInterpolated(double ticks) +{ + Clock += ticks; + DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE); + DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE); + return Clock >= 32.; } //========================================================================== @@ -300,7 +324,7 @@ Wiper_Melt::Wiper_Melt() y[0] = -(M_Random() & 15); for (int i = 1; i < WIDTH; ++i) { - y[i] = clamp(y[i-1] + (M_Random() % 3) - 1, -15, 0); + y[i] = clamp(y[i-1] + (double)(M_Random() % 3) - 1., -15., 0.); } } @@ -325,25 +349,25 @@ bool Wiper_Melt::Run(int ticks) { if (y[i] < HEIGHT) { - if (y[i] < 0) - y[i]++; - else if (y[i] < 16) - y[i] += y[i] + 1; + if (y[i] < 0.) + y[i] = y[i] + 1.; + else if (y[i] < 16.) + y[i] += y[i] + 1.; else - y[i] = min(y[i] + 8, HEIGHT); + y[i] = min(y[i] + 8., HEIGHT); done = false; } if (ticks == 0) { struct { int32_t x; - int32_t y; + double y; } dpt; struct { int32_t left; - int32_t top; + double top; int32_t right; - int32_t bottom; + double bottom; } rect; // Only draw for the final tick. @@ -351,7 +375,7 @@ bool Wiper_Melt::Run(int ticks) int w = startScreen->GetTexelWidth(); int h = startScreen->GetTexelHeight(); dpt.x = i * w / WIDTH; - dpt.y = max(0, y[i] * h / HEIGHT); + dpt.y = max(0., y[i] * (double)h / (double)HEIGHT); rect.left = dpt.x; rect.top = 0; rect.right = (i + 1) * w / WIDTH; @@ -366,6 +390,77 @@ bool Wiper_Melt::Run(int ticks) return done; } +//========================================================================== +// +// Wiper_Melt :: RunInterpolated +// +// Melts the old screen into the new one over 32 ticks (interpolated). +// +//========================================================================== + +bool Wiper_Melt::RunInterpolated(double ticks) +{ + bool done = false; + DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE); + + // Copy the old screen in vertical strips on top of the new one. + while (ticks > 0.) + { + done = true; + for (int i = 0; i < WIDTH; i++) + { + if (y[i] < (double)HEIGHT) + { + if (ticks > 0. && ticks < 1.) + { + if (y[i] < 0) + y[i] += ticks; + else if (y[i] < 16) + y[i] += (y[i] + 1) * ticks; + else + y[i] = min(y[i] + (8 * ticks), (double)HEIGHT); + } + else if (y[i] < 0.) + y[i] = y[i] + 1.; + else if (y[i] < 16.) + y[i] += y[i] + 1.; + else + y[i] = min(y[i] + 8., HEIGHT); + done = false; + } + } + ticks -= 1.; + } + for (int i = 0; i < WIDTH; i++) + { + struct { + int32_t x; + double y; + } dpt; + struct { + int32_t left; + double top; + int32_t right; + double bottom; + } rect; + + // Only draw for the final tick. + int w = startScreen->GetTexelWidth(); + double h = startScreen->GetTexelHeight(); + dpt.x = i * w / WIDTH; + dpt.y = max(0., y[i] * (double)h / (double)HEIGHT); + rect.left = dpt.x; + rect.top = 0; + rect.right = (i + 1) * w / WIDTH; + rect.bottom = h - dpt.y; + if (rect.bottom > rect.top) + { + DrawTexture(twod, startScreen, 0, dpt.y, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_ClipLeft, rect.left, DTA_ClipRight, rect.right, DTA_Masked, false, TAG_DONE); + } + } + return done; +} + //========================================================================== // // OpenGLFrameBuffer :: Wiper_Burn Constructor @@ -504,6 +599,7 @@ void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stops { // wipe update uint64_t wipestart, nowtime, diff; + double diff_frac; bool done; GSnd->SetSfxPaused(true, 1); @@ -519,20 +615,34 @@ void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stops do { - do + if (wiper->Interpolatable() && !cl_capfps) { - I_WaitVBL(2); nowtime = I_msTime(); - diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow. - } while (diff < 1); - wipestart = nowtime; - twod->Begin(screen->GetWidth(), screen->GetHeight()); - done = wiper->Run(1); - if (overlaydrawer) overlaydrawer(); - twod->End(); - screen->Update(); - twod->OnFrameDone(); - + diff_frac = (nowtime - wipestart) * 40. / 1000.; // Using 35 here feels too slow. + wipestart = nowtime; + twod->Begin(screen->GetWidth(), screen->GetHeight()); + done = wiper->RunInterpolated(diff_frac); + if (overlaydrawer) overlaydrawer(); + twod->End(); + screen->Update(); + twod->OnFrameDone(); + } + else + { + do + { + I_WaitVBL(2); + nowtime = I_msTime(); + diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow. + } while (diff < 1); + wipestart = nowtime; + twod->Begin(screen->GetWidth(), screen->GetHeight()); + done = wiper->Run(1); + if (overlaydrawer) overlaydrawer(); + twod->End(); + screen->Update(); + twod->OnFrameDone(); + } } while (!done); delete wiper; I_FreezeTime(false); diff --git a/src/common/audio/sound/i_sound.cpp b/src/common/audio/sound/i_sound.cpp index f859ae0a8..2f40333c0 100644 --- a/src/common/audio/sound/i_sound.cpp +++ b/src/common/audio/sound/i_sound.cpp @@ -62,6 +62,9 @@ CUSTOM_CVAR(Int, snd_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Int, snd_buffersize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Int, snd_hrtf, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Float, snd_footstepvolume, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) + + #if !defined(NO_OPENAL) #define DEF_BACKEND "openal" #else diff --git a/src/common/platform/posix/i_system.h b/src/common/platform/posix/i_system.h index e9e3f2a97..5b145eb8c 100644 --- a/src/common/platform/posix/i_system.h +++ b/src/common/platform/posix/i_system.h @@ -54,17 +54,6 @@ bool I_WriteIniFailed (const char* filename); class FGameTexture; bool I_SetCursor(FGameTexture *); -static inline char *strlwr(char *str) -{ - char *ptr = str; - while(*ptr) - { - *ptr = tolower(*ptr); - ++ptr; - } - return str; -} - inline int I_GetNumaNodeCount() { return 1; } inline int I_GetNumaNodeThreadCount(int numaNode) { return std::max(std::thread::hardware_concurrency(), 1); } inline void I_SetThreadNumaNode(std::thread &thread, int numaNode) { } diff --git a/src/common/scripting/backend/vmbuilder.cpp b/src/common/scripting/backend/vmbuilder.cpp index 71a766d9a..5437ca848 100644 --- a/src/common/scripting/backend/vmbuilder.cpp +++ b/src/common/scripting/backend/vmbuilder.cpp @@ -879,10 +879,18 @@ void FFunctionBuildList::Build() { if (!item.Code->CheckReturn()) { - auto newcmpd = new FxCompoundStatement(item.Code->ScriptPosition); - newcmpd->Add(item.Code); - newcmpd->Add(new FxReturnStatement(nullptr, item.Code->ScriptPosition)); - item.Code = newcmpd->Resolve(ctx); + if (ctx.ReturnProto == nullptr || !ctx.ReturnProto->ReturnTypes.Size()) + { + auto newcmpd = new FxCompoundStatement(item.Code->ScriptPosition); + newcmpd->Add(item.Code); + newcmpd->Add(new FxReturnStatement(nullptr, item.Code->ScriptPosition)); + item.Code = newcmpd->Resolve(ctx); + } + else + { + item.Code->ScriptPosition.Message(MSG_ERROR, "Missing return statement in %s", item.PrintableName.GetChars()); + continue; + } } item.Proto = ctx.ReturnProto; diff --git a/src/common/scripting/interface/vmnatives.cpp b/src/common/scripting/interface/vmnatives.cpp index 2b57ceb25..c778364ed 100644 --- a/src/common/scripting/interface/vmnatives.cpp +++ b/src/common/scripting/interface/vmnatives.cpp @@ -41,6 +41,7 @@ #include "c_cvars.h" #include "c_bind.h" #include "c_dispatch.h" +#include "m_misc.h" #include "menu.h" #include "vm.h" @@ -1032,6 +1033,17 @@ DEFINE_ACTION_FUNCTION(_CVar, FindCVar) ACTION_RETURN_POINTER(FindCVar(name.GetChars(), nullptr)); } +static int SaveConfig() +{ + return M_SaveDefaults(nullptr); +} + +DEFINE_ACTION_FUNCTION_NATIVE(_CVar, SaveConfig, SaveConfig) +{ + PARAM_PROLOGUE; + ACTION_RETURN_INT(M_SaveDefaults(nullptr)); +} + //============================================================================= // // diff --git a/src/g_game.cpp b/src/g_game.cpp index ab3cac323..8a189bf17 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -96,6 +96,8 @@ static FRandom pr_dmspawn ("DMSpawn"); static FRandom pr_pspawn ("PlayerSpawn"); +extern int startpos, laststartpos; + bool WriteZip(const char* filename, const FileSys::FCompressedBuffer* content, size_t contentcount); bool G_CheckDemoStatus (void); void G_ReadDemoTiccmd (ticcmd_t *cmd, int player); @@ -2146,7 +2148,9 @@ void G_DoLoadGame () arc("ticrate", time[0]) ("leveltime", time[1]) - ("globalfreeze", globalfreeze); + ("globalfreeze", globalfreeze) + ("startpos", startpos) + ("laststartpos", laststartpos); // dearchive all the modifications level.time = Scale(time[1], TICRATE, time[0]); @@ -2437,6 +2441,10 @@ void G_DoSaveGame (bool okForQuicksave, bool forceQuicksave, FString filename, c savegameglobals("leveltime", level.time); } + savegameglobals("globalfreeze", globalfreeze) + ("startpos", startpos) + ("laststartpos", laststartpos); + STAT_Serialize(savegameglobals); FRandom::StaticWriteRNGState(savegameglobals); P_WriteACSDefereds(savegameglobals); diff --git a/src/g_level.cpp b/src/g_level.cpp index ab4fa15c0..fbd500008 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -111,6 +111,8 @@ EXTERN_CVAR (Int, disableautosave) EXTERN_CVAR (String, playerclass) extern uint8_t globalfreeze, globalchangefreeze; +int startpos = 0; // [RH] Support for multiple starts per level +int laststartpos = 0; #define SNAP_ID MAKE_ID('s','n','A','p') #define DSNP_ID MAKE_ID('d','s','N','p') @@ -652,7 +654,9 @@ void G_InitNew (const char *mapname, bool bTitleLevel) gamestate = GS_LEVEL; } - G_DoLoadLevel (mapname, 0, false, !savegamerestore); + if (!savegamerestore) + startpos = laststartpos = 0; + G_DoLoadLevel (mapname, startpos, false, !savegamerestore); if (!savegamerestore && (gameinfo.gametype == GAME_Strife || (SBarInfoScript[SCRIPT_CUSTOM] != nullptr && SBarInfoScript[SCRIPT_CUSTOM]->GetGameType() == GAME_Strife))) { @@ -669,7 +673,6 @@ void G_InitNew (const char *mapname, bool bTitleLevel) // G_DoCompleted // static FString nextlevel; -static int startpos; // [RH] Support for multiple starts per level extern int NoWipe; // [RH] Don't wipe when travelling in hubs static int changeflags; static bool unloading; @@ -1100,7 +1103,6 @@ void G_DoCompleted (void) if (gamestate == GS_TITLELEVEL) { G_DoLoadLevel (nextlevel, startpos, false, false); - startpos = 0; viewactive = true; return; } @@ -1373,7 +1375,6 @@ void G_DoLoadLevel(const FString &nextmapname, int position, bool autosave, bool void FLevelLocals::DoLoadLevel(const FString &nextmapname, int position, bool autosave, bool newGame) { MapName = nextmapname; - static int lastposition = 0; int i; if (NextSkill >= 0) @@ -1385,9 +1386,9 @@ void FLevelLocals::DoLoadLevel(const FString &nextmapname, int position, bool au } if (position == -1) - position = lastposition; + position = laststartpos; else - lastposition = position; + laststartpos = position; Init(); StatusBar->DetachAllMessages (); @@ -1579,7 +1580,6 @@ void G_DoWorldDone (void) } primaryLevel->StartTravel (); G_DoLoadLevel (nextlevel, startpos, true, false); - startpos = 0; gameaction = ga_nothing; viewactive = true; } @@ -2407,6 +2407,24 @@ void FLevelLocals::ApplyCompatibility2() i_compatflags2 = GetCompatibility2(compatflags2) | ii_compatflags2; } +AActor* FLevelLocals::SelectActorFromTID(int tid, size_t index, AActor* defactor) +{ + if (tid == 0) + return defactor; + + AActor* actor = nullptr; + size_t cur = 0u; + auto it = GetActorIterator(tid); + while ((actor = it.Next()) != nullptr) + { + if (cur == index) + return actor; + ++cur; + } + + return nullptr; +} + //========================================================================== // IsPointInMap // diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 277849111..094223548 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -149,6 +149,7 @@ struct FLevelLocals int GetCompatibility2(int mask); void ApplyCompatibility(); void ApplyCompatibility2(); + AActor* SelectActorFromTID(int tid, size_t index, AActor* defactor); void Init(); @@ -166,8 +167,8 @@ private: void SerializePlayers(FSerializer &arc, bool skipload); void CopyPlayer(player_t *dst, player_t *src, const char *name); - void ReadOnePlayer(FSerializer &arc, bool skipload); - void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload); + void ReadOnePlayer(FSerializer &arc, bool fromHub); + void ReadMultiplePlayers(FSerializer &arc, int numPlayers, bool fromHub); void SerializeSounds(FSerializer &arc); void PlayerSpawnPickClass (int playernum); diff --git a/src/gamedata/p_terrain.cpp b/src/gamedata/p_terrain.cpp index 4d0efcff9..0bb11237f 100644 --- a/src/gamedata/p_terrain.cpp +++ b/src/gamedata/p_terrain.cpp @@ -77,13 +77,14 @@ enum ETerrainKeywords TR_DAMAGETIMEMASK, TR_FOOTCLIP, TR_STEPVOLUME, - TR_WALKINGSTEPTIME, - TR_RUNNINGSTEPTIME, + TR_WALKSTEPTICS, + TR_RUNSTEPTICS, TR_LEFTSTEPSOUNDS, TR_RIGHTSTEPSOUNDS, TR_LIQUID, TR_FRICTION, - TR_ALLOWPROTECTION + TR_ALLOWPROTECTION, + TR_STEPSOUNDS }; enum EGenericType @@ -95,7 +96,6 @@ enum EGenericType GEN_Splash, GEN_Float, GEN_Double, - GEN_Time, GEN_Bool, GEN_Int, GEN_Custom, @@ -179,14 +179,17 @@ static const char *TerrainKeywords[] = "damagetimemask", "footclip", "stepvolume", - "walkingsteptime", - "runningsteptime", + "walksteptics", + "runsteptics", "leftstepsounds", "rightstepsounds", "liquid", "friction", "allowprotection", "damageonland", + "stepsounds", + "stepdistance", + "stepdistanceminvel", NULL }; @@ -215,14 +218,17 @@ static FGenericParse TerrainParser[] = { GEN_Int, {myoffsetof(FTerrainDef, DamageTimeMask)} }, { GEN_Double, {myoffsetof(FTerrainDef, FootClip)} }, { GEN_Float, {myoffsetof(FTerrainDef, StepVolume)} }, - { GEN_Time, {myoffsetof(FTerrainDef, WalkStepTics)} }, - { GEN_Time, {myoffsetof(FTerrainDef, RunStepTics)} }, + { GEN_Int, {myoffsetof(FTerrainDef, WalkStepTics)} }, + { GEN_Int, {myoffsetof(FTerrainDef, RunStepTics)} }, { GEN_Sound, {myoffsetof(FTerrainDef, LeftStepSound)} }, { GEN_Sound, {myoffsetof(FTerrainDef, RightStepSound)} }, { GEN_Bool, {myoffsetof(FTerrainDef, IsLiquid)} }, { GEN_Custom, {(size_t)ParseFriction} }, { GEN_Bool, {myoffsetof(FTerrainDef, AllowProtection)} }, { GEN_Bool, {myoffsetof(FTerrainDef, DamageOnLand)} }, + { GEN_Sound, {myoffsetof(FTerrainDef, StepSound)} }, + { GEN_Double, {myoffsetof(FTerrainDef, StepDistance)} }, + { GEN_Double, {myoffsetof(FTerrainDef, StepDistanceMinVel)} }, }; @@ -597,11 +603,6 @@ static void GenericParse (FScanner &sc, FGenericParse *parser, const char **keyw SET_FIELD(double, sc.Float); break; - case GEN_Time: - sc.MustGetFloat (); - SET_FIELD (int, (int)(sc.Float * TICRATE)); - break; - case GEN_Bool: SET_FIELD (bool, true); break; @@ -747,3 +748,6 @@ DEFINE_FIELD(FTerrainDef, AllowProtection) DEFINE_FIELD(FTerrainDef, DamageOnLand) DEFINE_FIELD(FTerrainDef, Friction) DEFINE_FIELD(FTerrainDef, MoveFactor) +DEFINE_FIELD(FTerrainDef, StepSound) +DEFINE_FIELD(FTerrainDef, StepDistance) +DEFINE_FIELD(FTerrainDef, StepDistanceMinVel) \ No newline at end of file diff --git a/src/gamedata/p_terrain.h b/src/gamedata/p_terrain.h index a225b8a73..6890a38cf 100644 --- a/src/gamedata/p_terrain.h +++ b/src/gamedata/p_terrain.h @@ -118,6 +118,9 @@ struct FTerrainDef bool DamageOnLand; double Friction; double MoveFactor; + FSoundID StepSound; + double StepDistance; + double StepDistanceMinVel; }; extern TArray Splashes; diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index 453da9c41..bac63790c 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -504,6 +504,8 @@ enum SECMF_OVERLAPPING = 512, // floor and ceiling overlap and require special renderer action. SECMF_NOSKYWALLS = 1024, // Do not draw "sky walls" SECMF_LIFT = 2048, // For MBF monster AI + SECMF_HURTMONSTERS = 4096, // Monsters in this sector are hurt like players. + SECMF_HARMINAIR = 8192, // Actors in this sector are also hurt mid-air. }; enum @@ -1062,6 +1064,16 @@ public: return pos == floor ? floorplane : ceilingplane; } + void SetPlaneReflectivity(int pos, double val) + { + reflect[pos] = val; + } + + double GetPlaneReflectivity(int pos) + { + return reflect[pos]; + } + bool isSecret() const { return !!(Flags & SECF_SECRET); diff --git a/src/gamedata/umapinfo.cpp b/src/gamedata/umapinfo.cpp index 9ba72e61b..d574bdcb9 100644 --- a/src/gamedata/umapinfo.cpp +++ b/src/gamedata/umapinfo.cpp @@ -148,8 +148,22 @@ static int ParseStandardProperty(FScanner &scanner, UMapEntry *mape, int *id24_l } else if (!pname.CompareNoCase("label")) { - scanner.MustGetToken(TK_StringConst); - mape->label = scanner.String; + if (scanner.CheckToken(TK_Identifier)) + { + if (!stricmp(scanner.String, "clear")) + { + mape->label = "*"; + } + else + { + scanner.ScriptError("Either 'clear' or string constant expected"); + } + } + else + { + scanner.MustGetToken(TK_StringConst); + mape->label = scanner.String; + } } else if (!pname.CompareNoCase("next")) { diff --git a/src/maploader/udmf.cpp b/src/maploader/udmf.cpp index d604d6685..d2a415340 100644 --- a/src/maploader/udmf.cpp +++ b/src/maploader/udmf.cpp @@ -2050,6 +2050,14 @@ public: Flag(sec->Flags, SECF_HAZARD, key); break; + case NAME_hurtmonsters: + Flag(sec->MoreFlags, SECMF_HURTMONSTERS, key); + break; + + case NAME_harminair: + Flag(sec->MoreFlags, SECMF_HARMINAIR, key); + break; + case NAME_floorterrain: sec->terrainnum[sector_t::floor] = P_FindTerrain(CheckString(key)); break; diff --git a/src/namedef_custom.h b/src/namedef_custom.h index eebb8ca3a..661de6665 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -812,6 +812,8 @@ xx(damageinterval) xx(leakiness) xx(damageterraineffect) xx(damagehazard) +xx(hurtmonsters) +xx(harminair) xx(floorterrain) xx(ceilingterrain) xx(floor_reflect) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 339c1d437..b975e3398 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -627,8 +627,8 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload) { if (arc.BeginObject(nullptr)) { - const char *n = Players[i]->userinfo.GetName(); - arc.StringPtr("playername", n); + FString name = Players[i]->userinfo.GetName(); + arc("playername", name); Players[i]->Serialize(arc); arc.EndObject(); } @@ -651,7 +651,7 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload) } else { - ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload); + ReadMultiplePlayers(arc, numPlayers, skipload); } arc.EndArray(); @@ -680,53 +680,41 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload) // //========================================================================== -void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload) +void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool fromHub) { - int i; - const char *name = NULL; - bool didIt = false; + if (!arc.BeginObject(nullptr)) + return; - if (arc.BeginObject(nullptr)) + FString name = {}; + arc("playername", name); + player_t temp = {}; + temp.Serialize(arc); + + for (int i = 0; i < MAXPLAYERS; ++i) { - arc.StringPtr("playername", name); + if (!PlayerInGame(i)) + continue; - for (i = 0; i < MAXPLAYERS; ++i) + if (!fromHub) { - if (playeringame[i]) - { - if (!didIt) - { - didIt = true; - player_t playerTemp; - playerTemp.Serialize(arc); - if (!skipload) - { - // This temp player has undefined pitch limits, so set them to something - // that should leave the pitch stored in the savegame intact when - // rendering. The real pitch limits will be set by P_SerializePlayers() - // via a net command, but that won't be processed in time for a screen - // wipe, so we need something here. - playerTemp.MaxPitch = playerTemp.MinPitch = playerTemp.mo->Angles.Pitch; - CopyPlayer(Players[i], &playerTemp, name); - } - else - { - // we need the player actor, so that G_FinishTravel can destroy it later. - Players[i]->mo = playerTemp.mo; - } - } - else - { - if (Players[i]->mo != NULL) - { - Players[i]->mo->Destroy(); - Players[i]->mo = NULL; - } - } - } + // This temp player has undefined pitch limits, so set them to something + // that should leave the pitch stored in the savegame intact when + // rendering. The real pitch limits will be set by P_SerializePlayers() + // via a net command, but that won't be processed in time for a screen + // wipe, so we need something here. + temp.MaxPitch = temp.MinPitch = temp.mo->Angles.Pitch; + CopyPlayer(Players[i], &temp, name.GetChars()); } - arc.EndObject(); + else + { + // we need the player actor, so that G_FinishTravel can destroy it later. + Players[i]->mo = temp.mo; + } + + break; } + + arc.EndObject(); } //========================================================================== @@ -735,109 +723,96 @@ void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload) // //========================================================================== -void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload) +struct NetworkPlayerInfo { - // For two or more players, read each player into a temporary array. - int i, j; - const char **nametemp = new const char *[numPlayers]; - player_t *playertemp = new player_t[numPlayers]; - uint8_t *tempPlayerUsed = new uint8_t[numPlayers]; - uint8_t playerUsed[MAXPLAYERS]; + FString Name = {}; + player_t Info = {}; + bool bUsed = false; +}; - for (i = 0; i < numPlayers; ++i) +void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, bool fromHub) +{ + TArray tempPlayers = {}; + tempPlayers.Reserve(numPlayers); + TArray assignedPlayers = {}; + assignedPlayers.Reserve(MAXPLAYERS); + + // Read all the save game players into a temporary array + for (auto& p : tempPlayers) { - nametemp[i] = NULL; if (arc.BeginObject(nullptr)) { - arc.StringPtr("playername", nametemp[i]); - playertemp[i].Serialize(arc); + arc("playername", p.Name); + p.Info.Serialize(arc); arc.EndObject(); } - tempPlayerUsed[i] = 0; - } - for (i = 0; i < MAXPLAYERS; ++i) - { - playerUsed[i] = playeringame[i] ? 0 : 2; } - if (!skipload) + // Now try to match players from the savegame with players present + // based on their names. If two players in the savegame have the + // same name, then they are assigned to players in the current game + // on a first-come, first-served basis. + for (int i = 0; i < MAXPLAYERS; ++i) { - // Now try to match players from the savegame with players present - // based on their names. If two players in the savegame have the - // same name, then they are assigned to players in the current game - // on a first-come, first-served basis. - for (i = 0; i < numPlayers; ++i) - { - for (j = 0; j < MAXPLAYERS; ++j) - { - if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0) - { // Found a match, so copy our temp player to the real player - Printf("Found player %d (%s) at %d\n", i, nametemp[i], j); - CopyPlayer(Players[j], &playertemp[i], nametemp[i]); - playerUsed[j] = 1; - tempPlayerUsed[i] = 1; - break; - } - } - } + if (!PlayerInGame(i)) + continue; - // Any players that didn't have matching names are assigned to existing - // players on a first-come, first-served basis. - for (i = 0; i < numPlayers; ++i) + for (auto& p : tempPlayers) { - if (tempPlayerUsed[i] == 0) + if (!p.bUsed && !p.Name.Compare(Players[i]->userinfo.GetName())) { - for (j = 0; j < MAXPLAYERS; ++j) + // Found a match, so copy our temp player to the real player + if (!fromHub) { - if (playerUsed[j] == 0) - { - Printf("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName()); - CopyPlayer(&players[j], &playertemp[i], nametemp[i]); - playerUsed[j] = 1; - tempPlayerUsed[i] = 1; - break; - } + Printf("Found %s's (%d) data\n", Players[i]->userinfo.GetName(), i); + CopyPlayer(Players[i], &p.Info, p.Name.GetChars()); } - } - } - - // Make sure any extra players don't have actors spawned yet. Happens if the players - // present now got the same slots as they had in the save, but there are not as many - // as there were in the save. - for (j = 0; j < MAXPLAYERS; ++j) - { - if (playerUsed[j] == 0) - { - if (players[j].mo != NULL) + else { - players[j].mo->Destroy(); - players[j].mo = NULL; + Players[i]->mo = p.Info.mo; } - } - } - // Remove any temp players that were not used. Happens if there are fewer players - // than there were in the save, and they got shuffled. - for (i = 0; i < numPlayers; ++i) - { - if (tempPlayerUsed[i] == 0) - { - playertemp[i].mo->Destroy(); - playertemp[i].mo = NULL; + p.bUsed = true; + assignedPlayers[i] = true; + break; } } } - else + + // Any players that didn't have matching names are assigned to existing + // players on a first-come, first-served basis. + for (int i = 0; i < MAXPLAYERS; ++i) { - for (i = 0; i < numPlayers; ++i) + if (!PlayerInGame(i) || assignedPlayers[i]) + continue; + + for (auto& p : tempPlayers) { - players[i].mo = playertemp[i].mo; + if (!p.bUsed) + { + if (!fromHub) + { + Printf("Assigned %s (%d) to %s's data\n", Players[i]->userinfo.GetName(), i, p.Name.GetChars()); + CopyPlayer(Players[i], &p.Info, p.Name.GetChars()); + } + else + { + Players[i]->mo = p.Info.mo; + } + + p.bUsed = true; + break; + } } } - delete[] tempPlayerUsed; - delete[] playertemp; - delete[] nametemp; + // Remove any temp players that were not used. Happens if there are now + // less players in the game than there were in the save + for (auto& p : tempPlayers) + { + if (!p.bUsed) + p.Info.mo->Destroy(); + } } //========================================================================== diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 96d97b392..4c5ca980b 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -443,7 +443,9 @@ enum ActorFlag9 MF9_SHADOWBLOCK = 0x00000004, // [inkoalawetrust] Actors in the line of fire with this flag trigger the MF_SHADOW aiming penalty. MF9_SHADOWAIMVERT = 0x00000008, // [inkoalawetrust] Monster aim is also offset vertically when aiming at shadow actors. MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states + MF9_NOSECTORDAMAGE = 0x00000020, // [inkoalawetrust] Actor ignores any sector-based damage (i.e damaging floors, NOT crushers) MF9_ISPUFF = 0x00000040, // [AA] Set on actors by P_SpawnPuff + MF9_FORCESECTORDAMAGE = 0x00000080, // [inkoalawetrust] Actor ALWAYS takes hurt floor damage if there's any. Even if the floor doesn't have SECMF_HURTMONSTERS. }; // --- mobj.renderflags --- diff --git a/src/playsim/p_3dfloors.cpp b/src/playsim/p_3dfloors.cpp index dd5a2bb0a..cce58326c 100644 --- a/src/playsim/p_3dfloors.cpp +++ b/src/playsim/p_3dfloors.cpp @@ -198,39 +198,40 @@ void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flags, int //========================================================================== // -// P_PlayerOnSpecial3DFloor -// Checks to see if a player is standing on or is inside a 3D floor (water) +// P_ActorOnSpecial3DFloor +// Checks to see if an actor is standing on or is inside a 3D floor (water) // and applies any specials.. // //========================================================================== -void P_PlayerOnSpecial3DFloor(player_t* player) +void P_ActorOnSpecial3DFloor(AActor* victim) { - for(auto rover : player->mo->Sector->e->XFloor.ffloors) + for(auto rover : victim->Sector->e->XFloor.ffloors) { if (!(rover->flags & FF_EXISTS)) continue; if (rover->flags & FF_FIX) continue; - + if (!checkForSpecialSector(victim, rover->model)) continue; + // Check the 3D floor's type... if(rover->flags & FF_SOLID) { // Player must be on top of the floor to be affected... - if(player->mo->Z() != rover->top.plane->ZatPoint(player->mo)) continue; + if (victim->Z() != rover->top.plane->ZatPoint(victim)) continue; } else { //Water and DEATH FOG!!! heh if ((rover->flags & FF_NODAMAGE) || - player->mo->Z() > rover->top.plane->ZatPoint(player->mo) || - player->mo->Top() < rover->bottom.plane->ZatPoint(player->mo)) + victim->Z() > rover->top.plane->ZatPoint(victim) || + victim->Top() < rover->bottom.plane->ZatPoint(victim)) continue; } // Apply sector specials - P_PlayerInSpecialSector(player, rover->model); + P_ActorInSpecialSector(victim, rover->model,rover); // Apply flat specials (using the ceiling!) - P_PlayerOnSpecialFlat(player, rover->model->GetTerrain(rover->top.isceiling)); + P_ActorOnSpecialFlat(victim, rover->model->GetTerrain(rover->top.isceiling)); break; } diff --git a/src/playsim/p_3dfloors.h b/src/playsim/p_3dfloors.h index 05d222137..2dba7f713 100644 --- a/src/playsim/p_3dfloors.h +++ b/src/playsim/p_3dfloors.h @@ -113,8 +113,7 @@ struct lightlist_t -class player_t; -void P_PlayerOnSpecial3DFloor(player_t* player); +void P_ActorOnSpecial3DFloor(AActor* victim); bool P_CheckFor3DFloorHit(AActor * mo, double z, bool trigger); bool P_CheckFor3DCeilingHit(AActor * mo, double z, bool trigger); diff --git a/src/playsim/p_acs.cpp b/src/playsim/p_acs.cpp index d05f5dae3..89aee643d 100644 --- a/src/playsim/p_acs.cpp +++ b/src/playsim/p_acs.cpp @@ -4806,6 +4806,8 @@ enum EACSFunctions ACSF_GetSectorHealth, ACSF_GetLineHealth, ACSF_SetSubtitleNumber, + ACSF_GetNetID, + ACSF_SetActivatorByNetID, // Eternity's ACSF_GetLineX = 300, @@ -5380,6 +5382,13 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, int32_t *args, int & actor = Level->SingleActorFromTID(args[0], activator); return actor != NULL? DoubleToACS(actor->Vel.Z) : 0; + case ACSF_GetNetID: + MIN_ARG_COUNT(2); + actor = Level->SelectActorFromTID(args[0], args[1], activator); + if (argCount > 2) + actor = COPY_AAPTREX(Level, actor, args[2]); + return actor != nullptr ? actor->GetNetworkID() : NetworkEntityManager::WorldNetID; + case ACSF_SetPointer: MIN_ARG_COUNT(2); if (activator) @@ -5431,6 +5440,18 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, int32_t *args, int & } return 0; + case ACSF_SetActivatorByNetID: + MIN_ARG_COUNT(1); + actor = dyn_cast(NetworkEntityManager::GetNetworkEntity(args[0])); + if (argCount > 1) + actor = COPY_AAPTREX(Level, actor, args[1]); + if (actor != nullptr) + { + activator = actor; + return 1; + } + return 0; + case ACSF_GetActorViewHeight: MIN_ARG_COUNT(1); actor = Level->SingleActorFromTID(args[0], activator); diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index 459d267be..bdceae149 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -106,50 +106,45 @@ static const struct ColorList { {NULL, 0, 0, 0 } }; -inline particle_t *NewParticle (FLevelLocals *Level, bool replace = false) +static void FreeParticle(FLevelLocals* Level, particle_t* particle) { - particle_t *result = nullptr; - // [MC] Thanks to RaveYard and randi for helping me with this addition. - // Array's filled up - if (Level->InactiveParticles == NO_PARTICLE) + auto prev = particle->tprev == NO_PARTICLE? nullptr : &Level->Particles[particle->tprev]; + int pindex = (int)(particle - Level->Particles.Data()); + auto tnext = particle->tnext; + assert(!prev || (prev->tnext == pindex)); + if (prev) + prev->tnext = tnext; + else + Level->ActiveParticles = tnext; + + if (tnext != NO_PARTICLE) { - if (replace) - { - result = &Level->Particles[Level->OldestParticle]; + particle_t* next = &Level->Particles[tnext]; + assert(next->tprev == pindex); + next->tprev = particle->tprev; + } + if (Level->OldestParticle == pindex) + { + assert(tnext == NO_PARTICLE); + Level->OldestParticle = particle->tprev; + } + memset(particle, 0, sizeof(particle_t)); + particle->tnext = Level->InactiveParticles; + Level->InactiveParticles = pindex; +} - // There should be NO_PARTICLE for the oldest's tnext - if (result->tprev != NO_PARTICLE) - { - // tnext: youngest to oldest - // tprev: oldest to youngest - - // 2nd oldest -> oldest - particle_t *nbottom = &Level->Particles[result->tprev]; - nbottom->tnext = NO_PARTICLE; - - // now oldest becomes youngest - Level->OldestParticle = result->tprev; - result->tnext = Level->ActiveParticles; - result->tprev = NO_PARTICLE; - Level->ActiveParticles = uint32_t(result - Level->Particles.Data()); - - // youngest -> 2nd youngest - particle_t* ntop = &Level->Particles[result->tnext]; - ntop->tprev = Level->ActiveParticles; - } - // [MC] Future proof this by resetting everything when replacing a particle. - auto tnext = result->tnext; - auto tprev = result->tprev; - *result = {}; - result->tnext = tnext; - result->tprev = tprev; - } - return result; +static particle_t *NewParticle (FLevelLocals *Level, bool replace = false) +{ + // Array's filled up + if (Level->InactiveParticles == NO_PARTICLE && Level->OldestParticle != NO_PARTICLE) + { + if (!replace) return nullptr; + FreeParticle(Level, &Level->Particles[Level->OldestParticle]); } // Array isn't full. uint32_t current = Level->ActiveParticles; - result = &Level->Particles[Level->InactiveParticles]; + auto result = &Level->Particles[Level->InactiveParticles]; Level->InactiveParticles = result->tnext; result->tnext = current; result->tprev = NO_PARTICLE; @@ -310,19 +305,7 @@ void P_ThinkParticles (FLevelLocals *Level) particle->size += particle->sizestep; if (particle->alpha <= 0 || --particle->ttl <= 0 || (particle->size <= 0)) { // The particle has expired, so free it - *particle = {}; - if (prev) - prev->tnext = i; - else - Level->ActiveParticles = i; - - if (i != NO_PARTICLE) - { - particle_t *next = &Level->Particles[i]; - next->tprev = particle->tprev; - } - particle->tnext = Level->InactiveParticles; - Level->InactiveParticles = (int)(particle - Level->Particles.Data()); + FreeParticle(Level, particle); continue; } diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index f87c28654..120f37231 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -464,6 +464,12 @@ static int P_IsUnderDamage(AActor* actor) dir |= cl->getDirection(); } // Q: consider crushing 3D floors too? + // [inkoalawetrust] Check for sectors that can harm the actor. + if (!(actor->flags9 & MF9_NOSECTORDAMAGE) && seclist->m_sector->damageamount > 0) + { + if (seclist->m_sector->MoreFlags & SECMF_HARMINAIR || actor->isAtZ(seclist->m_sector->LowestFloorAt(actor)) || actor->waterlevel) + return (actor->player || (actor->player == nullptr && seclist->m_sector->MoreFlags & SECMF_HURTMONSTERS)) ? -1 : 0; + } } return dir; } @@ -1303,6 +1309,161 @@ int P_IsVisible(AActor *lookee, AActor *other, INTBOOL allaround, FLookExParams return P_CheckSight(lookee, other, SF_SEEPASTSHOOTABLELINES); } +bool isTargetablePlayer(AActor *actor, player_t *player, INTBOOL allaround, void* lookparams) +{ + FLookExParams* params = (FLookExParams*)lookparams; + + if (!(player->mo->flags & MF_SHOOTABLE)) + return false; // not shootable (observer or dead) + + if (actor->IsFriend(player->mo)) + return false; // same +MF_FRIENDLY, ignore + + if (player->cheats & CF_NOTARGET) + return false; // no target + + if (player->health <= 0) + return false; // dead + + if (!P_IsVisible(actor, player->mo, allaround, params)) + return false; // out of sight + + // [RC] Well, let's let special monsters with this flag active be able to see + // the player then, eh? + if (!(actor->flags6 & MF6_SEEINVISIBLE)) + { + if ((player->mo->flags & MF_SHADOW && !(actor->Level->i_compatflags & COMPATF_INVISIBILITY)) || + player->mo->flags3 & MF3_GHOST) + { + if (player->mo->Distance2D(actor) > 128 && player->mo->Vel.XY().LengthSquared() < 5 * 5) + { // Player is sneaking - can't detect + return false; + } + if (pr_lookforplayers() < 225) + { // Player isn't sneaking, but still didn't detect + return false; + } + } + } + + return true; +} + +bool ValidEnemyInBlock(AActor* lookee, AActor* other, void* lookparams) +{ + FLookExParams* params = (FLookExParams*)lookparams; + + if (!(other->flags & MF_SHOOTABLE)) + return false; // not shootable (observer or dead) + + if (other == lookee) + return false; // is self + + if (other->health <= 0) + return false; // dead + + if (other->flags2 & MF2_DORMANT) + return false; // don't target dormant things + + if (!(other->flags3 & MF3_ISMONSTER)) + return false; // don't target it if it isn't a monster (could be a barrel) + + if (other->flags7 & MF7_NEVERTARGET) + return false; + + bool keepChecking = false; + if (lookee->flags & MF_FRIENDLY) + { + if (other->flags & MF_FRIENDLY) + { + if (!lookee->IsFriend(other)) + { + // This is somebody else's friend, so go after it + keepChecking = true; + } + else if (other->target != NULL && !(other->target->flags & MF_FRIENDLY)) + { + other = other->target; + if (!(other->flags & MF_SHOOTABLE) || + other->health <= 0 || + (other->flags2 & MF2_DORMANT)) + { + return false; + } + } + } + else + { + keepChecking = true; + } + } + else if (lookee->flags8 & MF8_SEEFRIENDLYMONSTERS && other->flags & MF_FRIENDLY) + { + keepChecking = true; + } + + // [MBF] If the monster is already engaged in a one-on-one attack + // with a healthy friend, don't attack around 60% the time. + + // [GrafZahl] This prevents friendlies from attacking all the same + // target. + + if (keepChecking) + { + AActor* targ = other->target; + if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend(targ) && + targ->health * 2 >= targ->SpawnHealth()) + { + return false; + } + } + + // [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here? + + if (!keepChecking || !P_IsVisible(lookee, other, true, params)) + return false; // out of sight + + return true; +} + +//============================================================================ +// +// LookForEnemiesEx +// +// [inkoalawetrust] Return a script array of all valid enemies of the caller +// in range. For ZScript. +// +//============================================================================ + +DEFINE_ACTION_FUNCTION(AActor, LookForEnemiesEx) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OUTPOINTER(targets,TArray); + PARAM_FLOAT(range); + PARAM_BOOL(noPlayers); + PARAM_BOOL(allaround); + PARAM_POINTER(params, FLookExParams); + + if (targets == nullptr) + ThrowAbortException(X_WRITE_NIL,"No targets array passed"); + + if (range == -1) + range = self->friendlyseeblocks * FBlockmap::MAPBLOCKUNITS; + + FPortalGroupArray check(FPortalGroupArray::PGA_Full3d); + FMultiBlockThingsIterator it(check, self, range, false); + FMultiBlockThingsIterator::CheckResult cres; + + while (it.Next(&cres)) + { + if (cres.thing->player == nullptr && ValidEnemyInBlock(cres.thing, self, params) || + !noPlayers && cres.thing->player && isTargetablePlayer(self, cres.thing->player, allaround, params)) + targets->Push(cres.thing); + } + + ACTION_RETURN_INT(targets->Size()); +} + //--------------------------------------------------------------------------- // // FUNC P_LookForMonsters @@ -1546,80 +1707,10 @@ AActor *LookForEnemiesInBlock (AActor *lookee, int index, void *extparam) for (block = lookee->Level->blockmap.blocklinks[index]; block != NULL; block = block->NextActor) { - link = block->Me; - - if (!(link->flags & MF_SHOOTABLE)) - continue; // not shootable (observer or dead) - - if (link == lookee) + if (!ValidEnemyInBlock(lookee, block->Me, params)) continue; - if (link->health <= 0) - continue; // dead - - if (link->flags2 & MF2_DORMANT) - continue; // don't target dormant things - - if (!(link->flags3 & MF3_ISMONSTER)) - continue; // don't target it if it isn't a monster (could be a barrel) - - if (link->flags7 & MF7_NEVERTARGET) - continue; - - other = NULL; - if (lookee->flags & MF_FRIENDLY) - { - if (link->flags & MF_FRIENDLY) - { - if (!lookee->IsFriend(link)) - { - // This is somebody else's friend, so go after it - other = link; - } - else if (link->target != NULL && !(link->target->flags & MF_FRIENDLY)) - { - other = link->target; - if (!(other->flags & MF_SHOOTABLE) || - other->health <= 0 || - (other->flags2 & MF2_DORMANT)) - { - other = NULL;; - } - } - } - else - { - other = link; - } - } - else if (lookee->flags8 & MF8_SEEFRIENDLYMONSTERS && link->flags & MF_FRIENDLY) - { - other = link; - } - - // [MBF] If the monster is already engaged in a one-on-one attack - // with a healthy friend, don't attack around 60% the time. - - // [GrafZahl] This prevents friendlies from attacking all the same - // target. - - if (other) - { - AActor *targ = other->target; - if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend (targ) && - targ->health*2 >= targ->SpawnHealth()) - { - continue; - } - } - - // [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here? - - if (other == NULL || !P_IsVisible (lookee, other, true, params)) - continue; // out of sight - - - return other; + return block->Me; } return NULL; } @@ -1819,38 +1910,8 @@ int P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params) player = actor->Level->Players[pnum]; - if (!(player->mo->flags & MF_SHOOTABLE)) - continue; // not shootable (observer or dead) - - if (actor->IsFriend(player->mo)) - continue; // same +MF_FRIENDLY, ignore - - if (player->cheats & CF_NOTARGET) - continue; // no target - - if (player->health <= 0) - continue; // dead - - if (!P_IsVisible (actor, player->mo, allaround, params)) - continue; // out of sight - - // [RC] Well, let's let special monsters with this flag active be able to see - // the player then, eh? - if(!(actor->flags6 & MF6_SEEINVISIBLE)) - { - if ((player->mo->flags & MF_SHADOW && !(actor->Level->i_compatflags & COMPATF_INVISIBILITY)) || - player->mo->flags3 & MF3_GHOST) - { - if (player->mo->Distance2D (actor) > 128 && player->mo->Vel.XY().LengthSquared() < 5*5) - { // Player is sneaking - can't detect - continue; - } - if (pr_lookforplayers() < 225) - { // Player isn't sneaking, but still didn't detect - continue; - } - } - } + if (!isTargetablePlayer(actor, player, allaround, params)) + continue; // [RH] Need to be sure the reactiontime is 0 if the monster is // leaving its goal to go after a player. diff --git a/src/playsim/p_local.h b/src/playsim/p_local.h index 6d02d2a56..584cfc43f 100644 --- a/src/playsim/p_local.h +++ b/src/playsim/p_local.h @@ -352,7 +352,7 @@ void P_TraceBleed (int damage, AActor *target, AActor *missile); // missile ver void P_TraceBleed(int damage, FTranslatedLineTarget *t, AActor *puff); // hitscan version void P_TraceBleed (int damage, AActor *target); // random direction version bool P_HitFloor (AActor *thing); -bool P_HitWater (AActor *thing, sector_t *sec, const DVector3 &pos, bool checkabove = false, bool alert = true, bool force = false); +bool P_HitWater (AActor *thing, sector_t *sec, const DVector3 &pos, bool checkabove = false, bool alert = true, bool force = false, int flags = 0); struct FRailParams diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 1b9422baf..78cd6b947 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -4435,6 +4435,14 @@ void AActor::Tick () // must have been removed if (ObjectFlags & OF_EuthanizeMe) return; } + //[inkoalawetrust] Genericized level damage handling that makes sector, 3D floor, and TERRAIN flat damage affect monsters and other NPCs too. + P_ActorOnSpecial3DFloor(this); //3D floors must be checked separately to see if their control sector allows non-player damage + if (checkForSpecialSector(this,Sector)) + { + P_ActorInSpecialSector(this,Sector); + if (!isAbove(Sector->floorplane.ZatPoint(this)) || waterlevel) // Actor must be touching the floor for TERRAIN flats. + P_ActorOnSpecialFlat(this, P_GetThingFloorType(this)); + } if (tics != -1) { @@ -6652,7 +6660,13 @@ int P_GetThingFloorType (AActor *thing) // Returns true if hit liquid and splashed, false if not. //--------------------------------------------------------------------------- -bool P_HitWater (AActor * thing, sector_t * sec, const DVector3 &pos, bool checkabove, bool alert, bool force) +enum HitWaterFlags +{ + THW_SMALL = 1 << 0, + THW_NOVEL = 1 << 1, +}; + +bool P_HitWater (AActor * thing, sector_t * sec, const DVector3 &pos, bool checkabove, bool alert, bool force, int flags) { if (thing->player && (thing->player->cheats & CF_PREDICTING)) return false; @@ -6740,13 +6754,13 @@ foundone: // Don't splash for living things with small vertical velocities. // There are levels where the constant splashing from the monsters gets extremely annoying - if (((thing->flags3&MF3_ISMONSTER || thing->player) && thing->Vel.Z >= -6) && !force) + if (!(flags & THW_NOVEL) && ((thing->flags3 & MF3_ISMONSTER || thing->player) && thing->Vel.Z >= -6) && !force) return Terrains[terrainnum].IsLiquid; splash = &Splashes[splashnum]; // Small splash for small masses - if (thing->Mass < 10) + if (flags & THW_SMALL || thing->Mass < 10) smallsplash = true; if (!(thing->flags3 & MF3_DONTSPLASH)) @@ -6811,7 +6825,8 @@ DEFINE_ACTION_FUNCTION(AActor, HitWater) PARAM_BOOL(checkabove); PARAM_BOOL(alert); PARAM_BOOL(force); - ACTION_RETURN_BOOL(P_HitWater(self, sec, DVector3(x, y, z), checkabove, alert, force)); + PARAM_INT(flags); + ACTION_RETURN_BOOL(P_HitWater(self, sec, DVector3(x, y, z), checkabove, alert, force, flags)); } diff --git a/src/playsim/p_spec.cpp b/src/playsim/p_spec.cpp index 6ffc2d9af..3851bd748 100644 --- a/src/playsim/p_spec.cpp +++ b/src/playsim/p_spec.cpp @@ -100,7 +100,7 @@ #include "c_console.h" #include "p_spec_thinkers.h" -static FRandom pr_playerinspecialsector ("PlayerInSpecialSector"); +static FRandom pr_actorinspecialsector ("ActorInSpecialSector"); EXTERN_CVAR(Bool, cl_predict_specials) EXTERN_CVAR(Bool, forcewater) @@ -419,23 +419,26 @@ bool P_PredictLine(line_t *line, AActor *mo, int side, int activationType) } // -// P_PlayerInSpecialSector +// P_ActorInSpecialSector // Called every tic frame -// that the player origin is in a special sector +// that the actor origin is in a special sector // -void P_PlayerInSpecialSector (player_t *player, sector_t * sector) +void P_ActorInSpecialSector (AActor *victim, sector_t * sector, F3DFloor* Ffloor) { if (sector == NULL) - { - // Falling, not all the way down yet? - sector = player->mo->Sector; - if (!player->mo->isAtZ(sector->LowestFloorAt(player->mo)) - && !player->mo->waterlevel) - { - return; - } - } + sector = victim->Sector; + // Falling, not all the way down yet? + bool evilAir = (sector->MoreFlags & SECMF_HARMINAIR); + bool SolidFfloor = Ffloor != nullptr && (Ffloor->flags & FF_SOLID); + if ((!evilAir && !(Ffloor != nullptr && !SolidFfloor)) && !victim->waterlevel) + { + // [inkoalawetrust] Check for 3D floors differently, because non-FF_INVERTSECTOR ffloors have their floor plane as the 3D floor BOTTOM. + double theZ = Ffloor == nullptr ? sector->LowestFloorAt(victim) : Ffloor->top.plane->ZatPoint(victim); + if (!victim->isAtZ(theZ)) + return; + } + // Has hit ground. auto Level = sector->Level; @@ -445,7 +448,7 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector) if (sector->damageinterval <= 0) sector->damageinterval = 32; // repair invalid damageinterval values - if (sector->Flags & (SECF_EXIT1 | SECF_EXIT2)) + if (victim->player && sector->Flags & (SECF_EXIT1 | SECF_EXIT2)) { for (int i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) @@ -464,7 +467,7 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector) // different damage types yet, so that's not happening for now. // [MK] account for subclasses that may have "Full" protection (i.e.: prevent leaky damage) int ironfeet = 0; - for (auto i = player->mo->Inventory; i != NULL; i = i->Inventory) + for (auto i = victim->Inventory; i != NULL; i = i->Inventory) { if (i->IsKindOf(NAME_PowerIronFeet)) { @@ -476,28 +479,28 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector) } } - if (sector->Flags & SECF_ENDGODMODE) player->cheats &= ~CF_GODMODE; - if ((ironfeet == 0 || (ironfeet < 2 && pr_playerinspecialsector() < sector->leakydamage))) + if (victim->player && sector->Flags & SECF_ENDGODMODE) victim->player->cheats &= ~CF_GODMODE; + if ((ironfeet == 0 || (ironfeet < 2 && pr_actorinspecialsector() < sector->leakydamage))) { - if (sector->Flags & SECF_HAZARD) + if (victim->player && sector->Flags & SECF_HAZARD) { - player->hazardcount += sector->damageamount; - player->hazardtype = sector->damagetype; - player->hazardinterval = sector->damageinterval; + victim->player->hazardcount += sector->damageamount; + victim->player->hazardtype = sector->damagetype; + victim->player->hazardinterval = sector->damageinterval; } else if (Level->time % sector->damageinterval == 0) { - if (!(player->cheats & (CF_GODMODE | CF_GODMODE2))) + if (!(victim->player && victim->player->cheats & (CF_GODMODE | CF_GODMODE2))) { - P_DamageMobj(player->mo, NULL, NULL, sector->damageamount, sector->damagetype); + P_DamageMobj(victim, NULL, NULL, sector->damageamount, sector->damagetype); } - if ((sector->Flags & SECF_ENDLEVEL) && player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT))) + if (victim->player && (sector->Flags & SECF_ENDLEVEL) && victim->player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT))) { Level->ExitLevel(0, false); } if (sector->Flags & SECF_DMGTERRAINFX) { - P_HitWater(player->mo, player->mo->Sector, player->mo->Pos(), false, true, true); + P_HitWater(victim, victim->Sector, victim->Pos(), false, true, true); } } } @@ -506,14 +509,14 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector) { if (Level->time % sector->damageinterval == 0) { - P_GiveBody(player->mo, -sector->damageamount, 100); + P_GiveBody(victim, -sector->damageamount, 100); } } - if (sector->isSecret()) + if (victim->player && sector->isSecret()) { sector->ClearSecret(); - P_GiveSecret(Level, player->mo, true, true, sector->Index()); + P_GiveSecret(Level, victim, true, true, sector->Index()); } } @@ -652,13 +655,13 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, GiveSecret) //============================================================================ // -// P_PlayerOnSpecialFlat +// P_ActorOnSpecialFlat // //============================================================================ -void P_PlayerOnSpecialFlat (player_t *player, int floorType) +void P_ActorOnSpecialFlat (AActor *victim, int floorType) { - auto Level = player->mo->Level; + auto Level = victim->Level; if (Terrains[floorType].DamageAmount && !(Level->time % (Terrains[floorType].DamageTimeMask+1))) @@ -668,7 +671,7 @@ void P_PlayerOnSpecialFlat (player_t *player, int floorType) if (Terrains[floorType].AllowProtection) { auto pitype = PClass::FindActor(NAME_PowerIronFeet); - for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory) + for (ironfeet = victim->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory) { if (ironfeet->IsKindOf (pitype)) break; @@ -678,20 +681,18 @@ void P_PlayerOnSpecialFlat (player_t *player, int floorType) int damage = 0; if (ironfeet == NULL) { - damage = P_DamageMobj (player->mo, NULL, NULL, Terrains[floorType].DamageAmount, + damage = P_DamageMobj (victim, NULL, NULL, Terrains[floorType].DamageAmount, Terrains[floorType].DamageMOD); } if (damage > 0 && Terrains[floorType].Splash != -1) { - S_Sound (player->mo, CHAN_AUTO, 0, + S_Sound (victim, CHAN_AUTO, 0, Splashes[Terrains[floorType].Splash].NormalSplashSound, 1, ATTN_IDLE); } } } - - // // P_UpdateSpecials // Animate planes, scroll walls, etc. diff --git a/src/playsim/p_spec.h b/src/playsim/p_spec.h index cc7d990a9..3ef1aef9e 100644 --- a/src/playsim/p_spec.h +++ b/src/playsim/p_spec.h @@ -35,6 +35,7 @@ #include "dsectoreffect.h" #include "doomdata.h" #include "r_state.h" +#include "d_player.h" class FScanner; struct level_info_t; @@ -90,13 +91,22 @@ bool P_ActivateLine (line_t *ld, AActor *mo, int side, int activationType, DVect bool P_TestActivateLine (line_t *ld, AActor *mo, int side, int activationType, DVector3 *optpos = NULL); bool P_PredictLine (line_t *ld, AActor *mo, int side, int activationType); -void P_PlayerInSpecialSector (player_t *player, sector_t * sector=NULL); -void P_PlayerOnSpecialFlat (player_t *player, int floorType); +void P_ActorInSpecialSector (AActor *victim, sector_t * sector = NULL, F3DFloor* Ffloor = NULL); +void P_ActorOnSpecialFlat (AActor *victim, int floorType); void P_SectorDamage(FLevelLocals *Level, int tag, int amount, FName type, PClassActor *protectClass, int flags); void P_SetSectorFriction (FLevelLocals *level, int tag, int amount, bool alterFlag); double FrictionToMoveFactor(double friction); void P_GiveSecret(FLevelLocals *Level, AActor *actor, bool printmessage, bool playsound, int sectornum); +inline bool checkForSpecialSector(AActor* mo, sector_t* sec) +{ + bool afsdnope = !!(mo->flags9 & MF9_NOSECTORDAMAGE); + bool afsdforce = !!(mo->flags9 & MF9_FORCESECTORDAMAGE); + bool sfhurtmonsters = !!(sec->MoreFlags & SECMF_HURTMONSTERS); + bool isplayer = (mo->player != nullptr) && (mo == mo->player->mo); + return ((!afsdnope || afsdforce) && (isplayer || sfhurtmonsters || afsdforce)); +} + // // getNextSector() // Return sector_t * of sector next to current. diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index d3294ffd6..a98a7c523 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -1204,15 +1204,6 @@ DEFINE_ACTION_FUNCTION(APlayerPawn, CheckMusicChange) void P_CheckEnvironment(player_t *player) { - P_PlayerOnSpecial3DFloor(player); - P_PlayerInSpecialSector(player); - - if (!player->mo->isAbove(player->mo->Sector->floorplane.ZatPoint(player->mo)) || - player->mo->waterlevel) - { - // Player must be touching the floor - P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo)); - } if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) && player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && player->mo->alternative == nullptr && player->mo->waterlevel == 0) diff --git a/src/rendering/hwrenderer/scene/hw_bsp.cpp b/src/rendering/hwrenderer/scene/hw_bsp.cpp index cbf6a4bd6..a86e09177 100644 --- a/src/rendering/hwrenderer/scene/hw_bsp.cpp +++ b/src/rendering/hwrenderer/scene/hw_bsp.cpp @@ -289,12 +289,12 @@ void HWDrawInfo::AddLine (seg_t *seg, bool portalclip, FRenderState& state) angle_t startAngleR = clipperr.PointToPseudoAngle(seg->v2->fX(), seg->v2->fY()); angle_t endAngleR = clipperr.PointToPseudoAngle(seg->v1->fX(), seg->v1->fY()); - if(r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && (startAngleR - endAngleR >= ANGLE_180)) + if(Viewpoint.IsAllowedOoB() && r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && (startAngleR - endAngleR >= ANGLE_180)) { if (!seg->backsector) clipperr.SafeAddClipRange(startAngleR, endAngleR); else if((seg->sidedef != nullptr) && !uint8_t(seg->sidedef->Flags & WALLF_POLYOBJ) && (currentsector->sectornum != seg->backsector->sectornum)) { - if (in_area == area_default) in_area = hw_CheckViewArea(seg->v2, seg->v1, seg->frontsector, seg->backsector); + if (in_area == area_default) in_area = hw_CheckViewArea(seg->v1, seg->v2, seg->frontsector, seg->backsector); backsector = hw_FakeFlat(drawctx, seg->backsector, in_area, true); if (hw_CheckClip(seg->sidedef, currentsector, backsector)) clipperr.SafeAddClipRange(startAngleR, endAngleR); } @@ -1015,7 +1015,7 @@ void HWDrawInfo::RenderBSP(void *node, bool drawpsprites, FRenderState& state) // Give the DrawInfo the viewpoint in fixed point because that's what the nodes are. viewx = FLOAT2FIXED(Viewpoint.Pos.X); viewy = FLOAT2FIXED(Viewpoint.Pos.Y); - if (r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && Viewpoint.IsAllowedOoB() && (Viewpoint.camera->ViewPos->Flags & VPSF_ABSOLUTEOFFSET)) + if (r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && Viewpoint.IsAllowedOoB()) { if (Viewpoint.camera->tracer != nullptr) { diff --git a/src/scripting/decorate/thingdef_parse.cpp b/src/scripting/decorate/thingdef_parse.cpp index 19c8f38ab..03a249814 100644 --- a/src/scripting/decorate/thingdef_parse.cpp +++ b/src/scripting/decorate/thingdef_parse.cpp @@ -49,9 +49,6 @@ #include "v_text.h" #include "m_argv.h" #include "v_video.h" -#ifndef _MSC_VER -#include "i_system.h" // for strlwr() -#endif // !_MSC_VER void ParseOldDecoration(FScanner &sc, EDefinitionType def, PNamespace *ns); EXTERN_CVAR(Bool, strictdecorate); @@ -945,15 +942,12 @@ static void ParseActorProperty(FScanner &sc, Baggage &bag) "Spawn", "See", "Melee", "Missile", "Pain", "Death", "XDeath", "Burn", "Ice", "Raise", "Crash", "Crush", "Wound", "Disintegrate", "Heal", NULL }; - strlwr (sc.String); - FString propname = sc.String; if (sc.CheckString (".")) { sc.MustGetString (); propname += '.'; - strlwr (sc.String); propname += sc.String; } else diff --git a/src/scripting/decorate/thingdef_states.cpp b/src/scripting/decorate/thingdef_states.cpp index 570905741..d518d6769 100644 --- a/src/scripting/decorate/thingdef_states.cpp +++ b/src/scripting/decorate/thingdef_states.cpp @@ -45,9 +45,6 @@ #include "thingdef.h" #include "codegen.h" #include "backend/codegen_doom.h" -#ifndef _MSC_VER -#include "i_system.h" // for strlwr() -#endif // !_MSC_VER //========================================================================== //*** @@ -562,11 +559,8 @@ FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Bagg FxExpression* ParseAction(FScanner &sc, FState state, FString statestring, Baggage &bag) { - // Make the action name lowercase - strlwr (sc.String); - FxExpression *call = DoActionSpecials(sc, state, bag); - if (call != NULL) + if (call != nullptr) { return call; } diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index cf1d0eca1..fa8d66674 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -351,7 +351,9 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(MF9, SHADOWBLOCK, AActor, flags9), DEFINE_FLAG(MF9, SHADOWAIMVERT, AActor, flags9), DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9), + DEFINE_FLAG(MF9, NOSECTORDAMAGE, AActor, flags9), DEFINE_PROTECTED_FLAG(MF9, ISPUFF, AActor, flags9), //[AA] was spawned by SpawnPuff + DEFINE_FLAG(MF9, FORCESECTORDAMAGE, AActor, flags9), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index 9229a8dc3..690511865 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -869,6 +869,34 @@ DEFINE_ACTION_FUNCTION_NATIVE(_Sector, SetXOffset, SetXOffset) ACTION_RETURN_INT(self->GetLightLevel()); } + static void SetPlaneReflectivity(sector_t* self, int pos, double val) + { + if (pos < 0 || pos > 1) ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "pos must be either 0 or 1"); + self->SetPlaneReflectivity(pos, val); + } + + DEFINE_ACTION_FUNCTION_NATIVE(_Sector, SetPlaneReflectivity, SetPlaneReflectivity) + { + PARAM_SELF_STRUCT_PROLOGUE(sector_t); + PARAM_INT(pos); + PARAM_FLOAT(val) + SetPlaneReflectivity(self, pos, val); + return 0; + } + + static double GetPlaneReflectivity(sector_t* self, int pos) + { + if (pos < 0 || pos > 1) ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "pos must be either 0 or 1"); + return self->GetPlaneReflectivity(pos); + } + + DEFINE_ACTION_FUNCTION_NATIVE(_Sector, GetPlaneReflectivity, GetPlaneReflectivity) + { + PARAM_SELF_STRUCT_PROLOGUE(sector_t); + PARAM_INT(pos); + ACTION_RETURN_FLOAT(GetPlaneReflectivity(self, pos)); + } + static int PortalBlocksView(sector_t *self, int pos) { return self->PortalBlocksView(pos); @@ -2253,7 +2281,8 @@ void FormatMapName(FLevelLocals *self, int cr, FString *result) // If a label is specified, use it uncontitionally here. if (self->info->MapLabel.IsNotEmpty()) { - *result << self->info->MapLabel << ": "; + if (self->info->MapLabel.Compare("*")) + *result << self->info->MapLabel << ": "; } else if (am_showmaplabel == 1 || (am_showmaplabel == 2 && !ishub)) { diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index e80a50105..51b2f12de 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -78,6 +78,7 @@ DoomEdNums 9081 = SkyPicker 9082 = SectorSilencer 9083 = SkyCamCompat + 9084 = OrthographicCamera 9200 = Decal 9300 = "$PolyAnchor" 9301 = "$PolySpawn" diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index e7cdf917b..6237f85ff 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -773,7 +773,7 @@ class Actor : Thinker native native Actor SpawnPuff(class pufftype, vector3 pos, double hitdir, double particledir, int updown, int flags = 0, Actor victim = null); native void SpawnBlood (Vector3 pos1, double dir, int damage); native void BloodSplatter (Vector3 pos, double hitangle, bool axe = false); - native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false); + native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false, int flags = 0); native void PlaySpawnSound(Actor missile); native clearscope bool CountsAsKill() const; @@ -851,6 +851,7 @@ class Actor : Thinker native native bool LookForTid(bool allaround, LookExParams params = null); native bool LookForEnemies(bool allaround, LookExParams params = null); native bool LookForPlayers(bool allaround, LookExParams params = null); + native int LookForEnemiesEx(out Array targets, double range = -1, bool noPlayers = true, bool allaround = false, LookExParams params = null); native bool TeleportMove(Vector3 pos, bool telefrag, bool modifyactor = true); native clearscope double DistanceBySpeed(Actor other, double speed) const; native name GetSpecies(); diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index c2da5f46c..b320b6253 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -87,6 +87,8 @@ class PlayerPawn : Actor flagdef CanSuperMorph: PlayerFlags, 1; flagdef CrouchableMorph: PlayerFlags, 2; flagdef WeaponLevel2Ended: PlayerFlags, 3; + //PF_VOODOO_ZOMBIE + flagdef MakeFootsteps: PlayerFlags, 5; //[inkoalawetrust] Use footstep system virtual. enum EPrivatePlayerFlags { @@ -1690,6 +1692,7 @@ class PlayerPawn : Actor CheckPitch(); HandleMovement(); CalcHeight (); + if (bMakeFootsteps) MakeFootsteps(); if (!(player.cheats & CF_PREDICTING)) { @@ -1718,6 +1721,106 @@ class PlayerPawn : Actor } } + //--------------------------------------------------------------------------- + // + // Handle player footstep sounds. + // Default footstep handling. + // + //--------------------------------------------------------------------------- + + int footstepCounter; + double footstepLength; + bool footstepFoot; + + void DoFootstep(TerrainDef Ground) + { + Sound Step = Ground.StepSound; + + //Generic foot-agnostic sound takes precedence. + if(!Step) + { + //Apparently most people walk with their right foot first, so assume that here. + if (!footstepFoot) + { + Step = Ground.LeftStepSound; + } + else + { + Step = Ground.RightStepSound; + } + + footstepFoot = !footstepFoot; + } + + if(Step) + { + A_StartSound(Step, flags: CHANF_OVERLAP, volume: Ground.StepVolume * snd_footstepvolume); + } + + //Steps make splashes regardless. + bool Heavy = (Mass >= 200) ? 0 : THW_SMALL; //Big player makes big splash. + HitWater(CurSector, (Pos.XY, CurSector.FloorPlane.ZatPoint(Pos.XY)), true, false, flags: Heavy | THW_NOVEL); + } + + virtual void MakeFootsteps() + { + if(pos.z > floorz) return; + + let Ground = GetFloorTerrain(); + + if(Ground && (player.cmd.forwardMove != 0 || player.cmd.sideMove != 0)) + { + int Delay = (player.cmd.buttons & BT_RUN) ? Ground.RunStepTics : Ground.WalkStepTics; + + if((player.cmd.buttons ^ player.oldbuttons) & BT_RUN) + { // zero out counters when starting/stopping a run + footstepCounter = 0; + footstepLength = Ground.StepDistance; + } + + if(Ground.StepDistance > 0) + { // distance-based terrain + footstepCounter = 0; + + double moveVel = vel.xy.length(); + + if(moveVel > Ground.StepDistanceMinVel) + { + footstepLength += moveVel; + + while(footstepLength > Ground.StepDistance) + { + footstepLength -= Ground.StepDistance; + DoFootstep(Ground); + } + } + else + { + footstepLength = Ground.StepDistance; + } + + } + else if(Delay > 0) + { // delay-based terrain + footstepLength = 0; + + if(footstepCounter % Delay == 0) + { + DoFootstep(Ground); + } + + footstepCounter = (footstepCounter + 1) % Delay; + } + } + else + { + footstepCounter = 0; + footstepLength = Ground.StepDistance; + footstepFoot = false; + } + + } + //--------------------------------------------------------------------------- // // PROC P_BringUpWeapon diff --git a/wadsrc/static/zscript/actors/shared/camera.zs b/wadsrc/static/zscript/actors/shared/camera.zs index 463ad6178..11b036162 100644 --- a/wadsrc/static/zscript/actors/shared/camera.zs +++ b/wadsrc/static/zscript/actors/shared/camera.zs @@ -212,3 +212,35 @@ class SpectatorCamera : Actor } } } + +Class OrthographicCamera : Actor +{ + Default + { + +NOBLOCKMAP + +NOINTERACTION + CameraHeight 0; + RenderStyle "None"; + } + + override void PostBeginPlay() + { + Super.PostBeginPlay(); + UpdateViewPos(); + } + + override void Tick() + { + if (current != args[0]) + UpdateViewPos(); + + Super.Tick(); + } + + protected int current; + protected void UpdateViewPos() + { + current = args[0]; + SetViewPos((-abs(max(1.0, double(current))), 0, 0), VPSF_ORTHOGRAPHIC|VPSF_ALLOWOUTOFBOUNDS); + } +} diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index ebcb85f39..3aec0a6f1 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -1471,6 +1471,12 @@ enum ECompatFlags COMPATF2_VOODOO_ZOMBIES = 1 << 15, // allow playerinfo, playerpawn, and voodoo health to all be different, and allow monster targetting of 'dead' players that have positive health }; +enum HitWaterFlags +{ + THW_SMALL = 1 << 0, + THW_NOVEL = 1 << 1, +}; + const M_E = 2.7182818284590452354; // e const M_LOG2E = 1.4426950408889634074; // log_2 e const M_LOG10E = 0.43429448190325182765; // log_10 e diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 8f055e1ed..2d3d5a132 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -621,6 +621,9 @@ struct TerrainDef native native bool DamageOnLand; native double Friction; native double MoveFactor; + native Sound StepSound; + native double StepDistance; + native double StepDistanceMinVel; }; enum EPickStart diff --git a/wadsrc/static/zscript/engine/base.zs b/wadsrc/static/zscript/engine/base.zs index 18fe1b363..69f5d66fe 100644 --- a/wadsrc/static/zscript/engine/base.zs +++ b/wadsrc/static/zscript/engine/base.zs @@ -689,6 +689,7 @@ struct CVar native }; native static CVar FindCVar(Name name); + native static bool SaveConfig(); bool GetBool() { return GetInt(); } native int GetInt(); native double GetFloat(); diff --git a/wadsrc/static/zscript/mapdata.zs b/wadsrc/static/zscript/mapdata.zs index 5d45563eb..9031b3158 100644 --- a/wadsrc/static/zscript/mapdata.zs +++ b/wadsrc/static/zscript/mapdata.zs @@ -435,6 +435,11 @@ struct Sector native play SECMF_UNDERWATERMASK = 32+64, SECMF_DRAWN = 128, // sector has been drawn at least once SECMF_HIDDEN = 256, // Do not draw on textured automap + SECMF_OVERLAPPING = 512, // floor and ceiling overlap and require special renderer action. + SECMF_NOSKYWALLS = 1024, // Do not draw "sky walls" + SECMF_LIFT = 2048, // For MBF monster AI + SECMF_HURTMONSTERS = 4096, // Monsters in this sector are hurt like players. + SECMF_HARMINAIR = 8192, // Actors in this sector are also hurt mid-air. } native uint16 MoreFlags; @@ -452,6 +457,9 @@ struct Sector native play SECF_ENDLEVEL = 512, // ends level when health goes below 10 SECF_HAZARD = 1024, // Change to Strife's delayed damage handling. SECF_NOATTACK = 2048, // monsters cannot start attacks in this sector. + SECF_EXIT1 = 4096, + SECF_EXIT2 = 8192, + SECF_KILLMONSTERS = 16384,// Monsters in this sector are instantly killed. SECF_WASSECRET = 1 << 30, // a secret that was discovered SECF_SECRET = 1 << 31, // a secret sector @@ -545,6 +553,8 @@ struct Sector native play native void ChangeLightLevel(int newval); native void SetLightLevel(int newval); native clearscope int GetLightLevel() const; + native void SetPlaneReflectivity(int pos, double val); + native clearscope double GetPlaneReflectivity(int pos); native void AdjustFloorClip(); native clearscope bool IsLinked(Sector other, bool ceiling) const; diff --git a/wadsrc_extra/static/iwadinfo.txt b/wadsrc_extra/static/iwadinfo.txt index 985df8b5d..98cf868f6 100644 --- a/wadsrc_extra/static/iwadinfo.txt +++ b/wadsrc_extra/static/iwadinfo.txt @@ -709,15 +709,15 @@ Order // Order in the IWAD selection box "DOOM 2: L'Enfer sur Terre" "DOOM 2: BFG Edition" "DOOM 2: XBox Edition" - "DOOM 2: KEX Edition" "DOOM 2: Unity Edition" + "DOOM 2: KEX Edition" "The Ultimate DOOM" "DOOM Registered" "DOOM Shareware" "DOOM: BFG Edition" "DOOM: XBox Edition" - "DOOM: KEX Edition" "DOOM: Unity Edition" + "DOOM: KEX Edition" "Final Doom: Plutonia Experiment" "Final Doom: Plutonia Experiment: Unity Edition" "Final Doom: Plutonia Experiment: KEX Edition"