diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 12fdd56c5..d87f2756e 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -2236,6 +2236,21 @@ bool CallFunc_MusicRemap(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: return false; } +/*-------------------------------------------------- + bool CallFunc_Freeze(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Updates level freeze. +--------------------------------------------------*/ +bool CallFunc_Freeze(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + (void)argV; + (void)argC; + + P_SetFreezeLevel(argV[0] != 0); + + return false; +} + /*-------------------------------------------------- bool CallFunc_Get/SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index 3bfd32548..89ab3d351 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -103,6 +103,8 @@ bool CallFunc_DialogueSetSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, bool CallFunc_DialogueSetCustomSpeaker(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_DialogueNewText(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_Freeze(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); + bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_GetSideProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index d178c4873..48a16fdc8 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -181,6 +181,7 @@ Environment::Environment() addFuncDataACS0( 508, addCallFunc(CallFunc_MusicPlay)); addFuncDataACS0( 509, addCallFunc(CallFunc_MusicStopAll)); addFuncDataACS0( 510, addCallFunc(CallFunc_MusicRemap)); + addFuncDataACS0( 511, addCallFunc(CallFunc_Freeze)); addFuncDataACS0( 600, addCallFunc(CallFunc_DialogueSetSpeaker)); addFuncDataACS0( 601, addCallFunc(CallFunc_DialogueSetCustomSpeaker)); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 45076986f..163c91a78 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -534,6 +534,7 @@ void D_RegisterClientCommands(void) // add cheats COM_AddDebugCommand("noclip", Command_CheatNoClip_f); COM_AddDebugCommand("god", Command_CheatGod_f); + COM_AddDebugCommand("freeze", Command_CheatFreeze_f); COM_AddDebugCommand("setrings", Command_Setrings_f); COM_AddDebugCommand("setspheres", Command_Setspheres_f); COM_AddDebugCommand("setlives", Command_Setlives_f); @@ -5737,6 +5738,13 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) break; } + case CHEAT_FREEZE: { + const char *status = P_FreezeCheat() ? "off" : "on"; + P_SetFreezeCheat( !P_FreezeCheat() ); + CV_CheaterWarning(targetPlayer, va("freeze %s", status)); + break; + } + case NUMBER_OF_CHEATS: break; } diff --git a/src/k_dialogue.h b/src/k_dialogue.h index 7b4f7437e..4d6c05c79 100644 --- a/src/k_dialogue.h +++ b/src/k_dialogue.h @@ -25,6 +25,8 @@ void K_UnsetDialogue(void); void K_DrawDialogue(void); void K_TickDialogue(void); +boolean K_DialogueFreeze(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index c3fb8dd5a..8639524fc 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -48,6 +48,7 @@ private: bool syllable; bool dismissable; + bool freeze; void Init(void); //void Unset(void); diff --git a/src/k_follower.c b/src/k_follower.c index bed6518dc..4cbfb84b9 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -435,7 +435,7 @@ void K_HandleFollower(player_t *player) } else // follower exists, woo! { - if (player->follower->hitlag != 0) + if (P_MobjIsFrozen(player->follower)) { // Don't update frames in hitlag return; diff --git a/src/m_cheat.c b/src/m_cheat.c index b4cb60d83..e44735eb9 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -303,6 +303,14 @@ void Command_CheatGod_f(void) D_Cheat(consoleplayer, CHEAT_GOD); } +void Command_CheatFreeze_f(void) +{ + REQUIRE_CHEATS; + REQUIRE_INLEVEL; + + D_Cheat(consoleplayer, CHEAT_FREEZE); +} + void Command_Scale_f(void) { const double scaled = atof(COM_Argv(1)); diff --git a/src/m_cheat.h b/src/m_cheat.h index 1c1270e9b..a8d0505cc 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -41,6 +41,7 @@ typedef enum { CHEAT_RESPAWNAT, CHEAT_GIVEPOWERUP, CHEAT_SPHERES, + CHEAT_FREEZE, NUMBER_OF_CHEATS } cheat_t; @@ -74,6 +75,7 @@ void OP_ObjectplaceMovement(player_t *player); // void Command_CheatNoClip_f(void); void Command_CheatGod_f(void); +void Command_CheatFreeze_f(void); void Command_Savecheckpoint_f(void); void Command_Setrings_f(void); void Command_Setspheres_f(void); diff --git a/src/p_local.h b/src/p_local.h index 4c6b44449..c5ab6f37a 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -333,6 +333,8 @@ void P_FlashPal(player_t *pl, UINT16 type, UINT16 duration); #define PAL_RECYCLE 3 #define PAL_NUKE 4 +boolean P_MobjIsFrozen(mobj_t *mobj); + // // P_ENEMY // diff --git a/src/p_map.c b/src/p_map.c index 92a2a65d4..ff22d543a 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -573,7 +573,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; // Ignore the collision if BOTH things are in hitlag. - if (thing->hitlag > 0 && tm.thing->hitlag > 0) + if (P_MobjIsFrozen(thing) && P_MobjIsFrozen(tm.thing)) return BMIT_CONTINUE; if ((thing->flags & MF_NOCLIPTHING) || !(thing->flags & (MF_SOLID|MF_SPECIAL|MF_PAIN|MF_SHOOTABLE|MF_SPRING))) diff --git a/src/p_mobj.c b/src/p_mobj.c index 1a9b98dac..d23f60da3 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -10012,6 +10012,7 @@ void P_MobjThinker(mobj_t *mobj) mobj->flags2 &= ~(MF2_ALREADYHIT); // Don't run any thinker code while in hitlag + mobj->eflags &= ~(MFE_PAUSED); if ((mobj->player ? mobj->hitlag - mobj->player->nullHitlag : mobj->hitlag) > 0) { mobj->eflags |= MFE_PAUSED; @@ -10054,11 +10055,14 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->type == MT_HITLAG && mobj->hitlag == 0) mobj->renderflags &= ~RF_DONTDRAW; */ + } + if (P_MobjIsFrozen(mobj)) + { return; } - mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_DAMAGEHITLAG|MFE_SLOPELAUNCHED|MFE_PAUSED); + mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_DAMAGEHITLAG|MFE_SLOPELAUNCHED); // sal: what the hell? is there any reason this isn't done, like, literally ANYWHERE else? P_SetTarget(&tm.floorthing, NULL); diff --git a/src/p_mobj.h b/src/p_mobj.h index bf07a567c..585dff7c7 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -434,6 +434,8 @@ struct mobj_t INT32 script_args[NUM_SCRIPT_ARGS]; char *script_stringargs[NUM_SCRIPT_STRINGARGS]; + boolean frozen; + // WARNING: New fields must be added separately to savegame and Lua. }; diff --git a/src/p_saveg.c b/src/p_saveg.c index 6c61e7f6a..edbb346fe 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2542,7 +2542,7 @@ typedef enum MD2_WAYPOINTCAP = 1<<25, MD2_KITEMCAP = 1<<26, MD2_ITNEXT = 1<<27, - MD2_LASTMOMZ = 1<<28, + MD2_FROZEN = 1<<28, MD2_TERRAIN = 1<<29, MD2_WATERSKIP = 1<<30, MD2_LIGHTLEVEL = (INT32)(1U<<31), @@ -2733,7 +2733,7 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 } // not the default but the most probable - if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz != 0) + if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz != 0 || mobj->lastmomz != 0) diff |= MD_MOM; if (mobj->radius != mobj->info->radius) diff |= MD_RADIUS; @@ -2855,8 +2855,8 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 diff2 |= MD2_KITEMCAP; if (mobj->itnext) diff2 |= MD2_ITNEXT; - if (mobj->lastmomz) - diff2 |= MD2_LASTMOMZ; + if (mobj->frozen) + diff2 |= MD2_FROZEN; if (mobj->terrain != NULL || mobj->terrainOverlay != NULL) diff2 |= MD2_TERRAIN; @@ -2919,6 +2919,7 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 WRITEFIXED(save->p, mobj->momy); WRITEFIXED(save->p, mobj->momz); WRITEFIXED(save->p, mobj->pmomz); + WRITEFIXED(save->p, mobj->lastmomz); } if (diff & MD_RADIUS) WRITEFIXED(save->p, mobj->radius); @@ -3123,9 +3124,9 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 { WRITEINT32(save->p, mobj->dispoffset); } - if (diff2 & MD2_LASTMOMZ) + if (diff2 & MD2_FROZEN) { - WRITEINT32(save->p, mobj->lastmomz); + WRITEUINT8(save->p, mobj->frozen); } if (diff2 & MD2_TERRAIN) { @@ -4102,6 +4103,7 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) mobj->momy = READFIXED(save->p); mobj->momz = READFIXED(save->p); mobj->pmomz = READFIXED(save->p); + mobj->lastmomz = READFIXED(save->p); } // otherwise they're zero, and the memset took care of it if (diff & MD_RADIUS) @@ -4357,9 +4359,9 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { mobj->dispoffset = READINT32(save->p); } - if (diff2 & MD2_LASTMOMZ) + if (diff2 & MD2_FROZEN) { - mobj->lastmomz = READINT32(save->p); + mobj->frozen = (boolean)READUINT8(save->p); } if (diff2 & MD2_TERRAIN) { diff --git a/src/p_setup.c b/src/p_setup.c index 0dc168034..2dae4b5c5 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7539,6 +7539,9 @@ static void P_InitLevelSettings(void) leveltime = 0; modulothing = 0; + P_SetFreezeLevel(false); + P_SetFreezeCheat(false); + K_TimerReset(); nummaprings = 0; diff --git a/src/p_tick.c b/src/p_tick.c index f411aa382..957dc93f0 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -52,6 +52,63 @@ tic_t leveltime; boolean thinkersCompleted; +static boolean g_freezeCheat; +static boolean g_freezeLevel; + +boolean P_LevelIsFrozen(void) +{ + return (g_freezeLevel || g_freezeCheat); +} + +boolean P_FreezeCheat(void) +{ + return (g_freezeLevel || g_freezeCheat); +} + +void P_SetFreezeCheat(boolean value) +{ + g_freezeCheat = value; +} + +void P_SetFreezeLevel(boolean value) +{ + g_freezeLevel = value; +} + +boolean P_MobjIsFrozen(mobj_t *mobj) +{ + if (g_freezeCheat == true) + { + // freeze cheat + switch (mobj->type) + { + case MT_PLAYER: + { + break; + } + default: + { + return true; + } + } + } + + if (g_freezeLevel == true) + { + // level totally frozen + return true; + } + + if ((mobj->eflags & MFE_PAUSED) == MFE_PAUSED) + { + // hitlag + return true; + } + + // manual + return mobj->frozen; +} + INT32 P_AltFlip(INT32 n, tic_t tics) { return leveltime % (2 * tics) < tics ? n : -(n); diff --git a/src/p_tick.h b/src/p_tick.h index d05813c1c..58c154a1a 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -27,6 +27,12 @@ extern "C" { extern tic_t leveltime; extern boolean thinkersCompleted; +boolean P_LevelIsFrozen(void); +boolean P_FreezeCheat(void); +void P_SetFreezeCheat(boolean value); +void P_SetFreezeLevel(boolean value); +boolean P_MobjIsFrozen(mobj_t *mobj); + // Called by G_Ticker. Carries out all thinking of enemies and players. void Command_Numthinkers_f(void); void Command_CountMobjs_f(void); diff --git a/src/p_user.c b/src/p_user.c index 15b96298a..a8f2694bc 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3114,7 +3114,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall mo = player->mo; - if (mo->hitlag > 0 || player->playerstate == PST_DEAD) + if (P_MobjIsFrozen(mo) || player->playerstate == PST_DEAD) { // Do not move the camera while in hitlag! // The camera zooming out after you got hit makes it hard to focus on the vibration. @@ -4208,7 +4208,7 @@ void P_PlayerThink(player_t *player) ALL ABOVE THIS BLOCK OCCURS EVEN WITH HITLAG / ------------------------------------------ */ - if (player->mo->hitlag > 0) + if (P_MobjIsFrozen(player->mo)) { return; } @@ -4546,7 +4546,7 @@ void P_IncrementGriefValue(player_t *player, UINT32 *grief, const UINT32 griefMa INT32 progress = player->distancetofinishprev - player->distancetofinish; boolean exceptions = ( player->flashing != 0 - || player->mo->hitlag != 0 + || P_MobjIsFrozen(player->mo) || player->airtime > 3*TICRATE/2 || (player->justbumped > 0 && player->justbumped < bumptime-1) );