diff --git a/src/actor.h b/src/actor.h index afed3a51f..9d7651d33 100644 --- a/src/actor.h +++ b/src/actor.h @@ -1425,6 +1425,7 @@ public: void DeleteAttachedLights(); static void DeleteAllAttachedLights(); static void RecreateAllAttachedLights(); + bool isFrozen(); bool hasmodel; }; diff --git a/src/actorinlines.h b/src/actorinlines.h index abc17e1b3..3e4f7684b 100644 --- a/src/actorinlines.h +++ b/src/actorinlines.h @@ -107,7 +107,7 @@ inline DVector3 AActor::Vec2OffsetZ(double dx, double dy, double atz, bool absol { return{ X() + dx, Y() + dy, atz }; } - else + else { DVector2 v = Level->GetPortalOffsetPosition(X(), Y(), dx, dy); return DVector3(v, atz); @@ -133,7 +133,7 @@ inline DVector3 AActor::Vec3Offset(double dx, double dy, double dz, bool absolut return { X() + dx, Y() + dy, Z() + dz }; } else - { + { DVector2 v = Level->GetPortalOffsetPosition(X(), Y(), dx, dy); return DVector3(v, Z() + dz); } @@ -151,8 +151,30 @@ inline DVector3 AActor::Vec3Angle(double length, DAngle angle, double dz, bool a return{ X() + length * angle.Cos(), Y() + length * angle.Sin(), Z() + dz }; } else - { + { DVector2 v = Level->GetPortalOffsetPosition(X(), Y(), length*angle.Cos(), length*angle.Sin()); return DVector3(v, Z() + dz); } } + +inline bool AActor::isFrozen() +{ + if (!(flags5 & MF5_NOTIMEFREEZE)) + { + auto state = Level->isFrozen(); + if (state) + { + if (player == nullptr || player->Bot != nullptr) return true; + + // This is the only place in the entire game where the two freeze flags need different treatment. + // The time freezer flag also freezes other players, the global setting does not. + + if ((state & 1) && player->timefreezer == 0) + { + return true; + } + } + } + return false; +} + diff --git a/src/b_bot.cpp b/src/b_bot.cpp index 9ff12a8ab..2ca43dca6 100644 --- a/src/b_bot.cpp +++ b/src/b_bot.cpp @@ -49,6 +49,7 @@ #include "d_player.h" #include "w_wad.h" #include "vm.h" +#include "g_levellocals.h" IMPLEMENT_CLASS(DBot, false, true) @@ -137,7 +138,7 @@ void DBot::Tick () { Super::Tick (); - if (player->mo == nullptr || bglobal.freeze) + if (player->mo == nullptr || Level->isFrozen()) { return; } diff --git a/src/b_bot.h b/src/b_bot.h index 71b8252f8..a24f07a46 100644 --- a/src/b_bot.h +++ b/src/b_bot.h @@ -116,7 +116,7 @@ public: void ClearPlayer (int playernum, bool keepTeam); //(b_game.cpp) - void Main (); + void Main (FLevelLocals *Level); void Init (); void End(); bool SpawnBot (const char *name, int color = NOCOLOR); diff --git a/src/b_game.cpp b/src/b_game.cpp index 69f273174..58f78424d 100644 --- a/src/b_game.cpp +++ b/src/b_game.cpp @@ -129,7 +129,7 @@ FCajunMaster::~FCajunMaster() } //This function is called every tick (from g_game.c). -void FCajunMaster::Main () +void FCajunMaster::Main(FLevelLocals *Level) { BotThinkCycles.Reset(); @@ -137,7 +137,7 @@ void FCajunMaster::Main () return; //Add new bots? - if (wanted_botnum > botnum && !freeze) + if (wanted_botnum > botnum && !Level->isFrozen()) { if (t_join == ((wanted_botnum - botnum) * SPAWN_DELAY)) { diff --git a/src/decallib.cpp b/src/decallib.cpp index db423eae6..18c1c7cfd 100644 --- a/src/decallib.cpp +++ b/src/decallib.cpp @@ -1184,7 +1184,7 @@ void DDecalFader::Tick () } else { - if (Level->maptime < TimeToStartDecay || bglobal.freeze) + if (Level->maptime < TimeToStartDecay || Level->isFrozen()) { return; } @@ -1271,7 +1271,7 @@ void DDecalStretcher::Tick () Destroy (); return; } - if (Level->maptime < TimeToStart || bglobal.freeze) + if (Level->maptime < TimeToStart || Level->isFrozen()) { return; } @@ -1339,7 +1339,7 @@ void DDecalSlider::Tick () Destroy (); return; } - if (Level->maptime < TimeToStart || bglobal.freeze) + if (Level->maptime < TimeToStart || Level->isFrozen()) { return; } @@ -1407,7 +1407,7 @@ void DDecalColorer::Tick () } else { - if (Level->maptime < TimeToStartDecay || bglobal.freeze) + if (Level->maptime < TimeToStartDecay || Level->isFrozen()) { return; } diff --git a/src/g_game.cpp b/src/g_game.cpp index b6c4cd4ad..677ba8a30 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1112,7 +1112,7 @@ void G_Ticker () uint32_t rngsum = FRandom::StaticSumSeeds (); //Added by MC: For some of that bot stuff. The main bot function. - bglobal.Main (); + bglobal.Main (&level); for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 38d424868..143743e78 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -369,6 +369,11 @@ public: return it.Next(); } + int isFrozen() + { + return frozenstate; + } + sector_t *PointInSector(const DVector2 &pos) { return P_PointInSector(pos); @@ -497,6 +502,7 @@ public: bool FromSnapshot; // The current map was restored from a snapshot bool HasHeightSecs; // true if some Transfer_Heights effects are present in the map. If this is false, some checks in the renderer can be shortcut. bool HasDynamicLights; // Another render optimization for maps with no lights at all. + uint8_t frozenstate; double teamdamage; diff --git a/src/hwrenderer/scene/hw_sprites.cpp b/src/hwrenderer/scene/hw_sprites.cpp index 49ced7dfe..eb0874e7b 100644 --- a/src/hwrenderer/scene/hw_sprites.cpp +++ b/src/hwrenderer/scene/hw_sprites.cpp @@ -1198,7 +1198,7 @@ void GLSprite::ProcessParticle (HWDrawInfo *di, particle_t *particle, sector_t * const auto &vp = di->Viewpoint; double timefrac = vp.TicFrac; - if (paused || bglobal.freeze || (di->Level->flags2 & LEVEL2_FROZEN)) + if (paused || di->Level->isFrozen()) timefrac = 0.; float xvf = (particle->Vel.X) * timefrac; float yvf = (particle->Vel.Y) * timefrac; diff --git a/src/p_effect.cpp b/src/p_effect.cpp index c9e7d70d4..1fd04ebc3 100644 --- a/src/p_effect.cpp +++ b/src/p_effect.cpp @@ -243,7 +243,7 @@ void P_ThinkParticles () { particle = &Particles[i]; i = particle->tnext; - if (!particle->notimefreeze && ((bglobal.freeze) || (level.flags2 & LEVEL2_FROZEN))) + if (!particle->notimefreeze && level.isFrozen()) { prev = particle; continue; diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index bbd7a4eaa..9ec9a6339 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -3505,18 +3505,15 @@ void AActor::Tick () // apply velocity // ensure that the actor is not linked into the blockmap - if (!(flags5 & MF5_NOTIMEFREEZE)) + //Added by MC: Freeze mode. + if (isFrozen()) { - //Added by MC: Freeze mode. - if (bglobal.freeze || Level->flags2 & LEVEL2_FROZEN) + // Boss cubes shouldn't be accelerated by timefreeze + if (flags6 & MF6_BOSSCUBE) { - // Boss cubes shouldn't be accelerated by timefreeze - if (flags6 & MF6_BOSSCUBE) - { - special2++; - } - return; + special2++; } + return; } if (!Vel.isZero() || !(flags & MF_NOBLOCKMAP)) @@ -3555,27 +3552,16 @@ void AActor::Tick () return; } - if (!(flags5 & MF5_NOTIMEFREEZE)) + if (isFrozen()) { // Boss cubes shouldn't be accelerated by timefreeze if (flags6 & MF6_BOSSCUBE) { special2++; } - //Added by MC: Freeze mode. - if (bglobal.freeze && !(player && player->Bot == NULL)) - { - return; - } - - // Apply freeze mode. - if ((Level->flags2 & LEVEL2_FROZEN) && (player == NULL || player->timefreezer == 0)) - { - return; - } + return; } - if (effects & FX_ROCKET) { if (++smokecounter == 4) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 593aacb38..ceac468e9 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -965,7 +965,8 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload) ("impactdecalcount", ImpactDecalCount) ("scrolls", Scrolls) ("automap", automap) - ("interpolator", interpolator); + ("interpolator", interpolator) + ("frozenstate", frozenstate); // Hub transitions must keep the current total time @@ -978,6 +979,7 @@ void FLevelLocals::Serialize(FSerializer &arc, bool hubload) sky2texture = skytexture2; R_InitSkyMap(); AirControlChanged(); + bglobal.freeze = !!(frozenstate & 2); } Behaviors.SerializeModuleStates(arc); diff --git a/src/p_tick.cpp b/src/p_tick.cpp index 79fdf2621..65655b82b 100644 --- a/src/p_tick.cpp +++ b/src/p_tick.cpp @@ -99,6 +99,7 @@ void P_Ticker (void) { if (bglobal.changefreeze) { + level.frozenstate ^= 2; bglobal.freeze ^= 1; bglobal.changefreeze = 0; } @@ -134,8 +135,7 @@ void P_Ticker (void) P_ThinkParticles(); // [RH] make the particles think for (i = 0; imo->Level->isFrozen() && player->Bot != nullptr) + { + return; + } + if (debugfile && !(player->cheats & CF_PREDICTING)) { fprintf (debugfile, "tic %d for pl %d: (%f, %f, %f, %f) b:%02x p:%d y:%d f:%d s:%d u:%d\n", @@ -1682,7 +1688,7 @@ bool P_IsPlayerTotallyFrozen(const player_t *player) return gamestate == GS_TITLELEVEL || player->cheats & CF_TOTALLYFROZEN || - ((player->mo->Level->flags2 & LEVEL2_FROZEN) && player->timefreezer == 0); + player->mo->isFrozen(); } diff --git a/src/polyrenderer/scene/poly_particle.cpp b/src/polyrenderer/scene/poly_particle.cpp index d7db9be84..002daac52 100644 --- a/src/polyrenderer/scene/poly_particle.cpp +++ b/src/polyrenderer/scene/poly_particle.cpp @@ -35,7 +35,7 @@ EXTERN_CVAR(Int, gl_particles_style) void RenderPolyParticle::Render(PolyRenderThread *thread, particle_t *particle, subsector_t *sub, uint32_t stencilValue) { double timefrac = r_viewpoint.TicFrac; - if (paused || bglobal.freeze || (PolyRenderer::Instance()->Level->flags2 & LEVEL2_FROZEN)) + if (paused || r_viewpoint.ViewLevel->isFrozen()) timefrac = 0.; DVector3 pos = particle->Pos + (particle->Vel * timefrac); double psize = particle->size / 8.0; diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index f75594b05..ac260f8ff 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -229,7 +229,7 @@ void FModelRenderer::RenderFrameModels(const FSpriteModelFrame *smf, const FStat // [BB] To interpolate at more than 35 fps we take tic fractions into account. float ticFraction = 0.; // [BB] In case the tic counter is frozen we have to leave ticFraction at zero. - if (ConsoleState == c_up && menuactive != MENU_On && !(level.flags2 & LEVEL2_FROZEN)) + if (ConsoleState == c_up && menuactive != MENU_On && !level.isFrozen()) { ticFraction = I_GetTimeFrac(); } diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index ee7f3a89d..bb3a8918e 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -2682,7 +2682,32 @@ DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, Vec3Offset, Vec3Offset) ACTION_RETURN_VEC3(result); } +static int isFrozen(FLevelLocals *self) +{ + return self->isFrozen(); +} +DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, isFrozen, isFrozen) +{ + PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); + return isFrozen(self); +} + +void setFrozen(FLevelLocals *self, int on) +{ + self->frozenstate = (self->frozenstate & ~1) | !!on; + // For compatibility. The engine itself never checks this. + if (on) self->flags2 |= LEVEL2_FROZEN; + else self->flags2 &= ~LEVEL2_FROZEN; +} + +DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, setFrozen, setFrozen) +{ + PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); + PARAM_BOOL(on); + setFrozen(self, on); + return 0; +} //===================================================================================== // @@ -2764,6 +2789,7 @@ DEFINE_FIELD(FLevelLocals, outsidefogdensity) DEFINE_FIELD(FLevelLocals, skyfog) DEFINE_FIELD(FLevelLocals, pixelstretch) DEFINE_FIELD(FLevelLocals, deathsequence) + DEFINE_FIELD_BIT(FLevelLocals, flags, noinventorybar, LEVEL_NOINVENTORYBAR) DEFINE_FIELD_BIT(FLevelLocals, flags, monsterstelefrag, LEVEL_MONSTERSTELEFRAG) DEFINE_FIELD_BIT(FLevelLocals, flags, actownspecial, LEVEL_ACTOWNSPECIAL) diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index dd833e744..68cf3560c 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -1654,6 +1654,18 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, CheckFor3DCeilingHit, CheckFor3DCeilingHit +static int isFrozen(AActor *self) +{ + return self->isFrozen(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, isFrozen, isFrozen) +{ + PARAM_SELF_PROLOGUE(AActor); + ACTION_RETURN_BOOL(isFrozen(self)); +} + + //=========================================================================== // // PlayerPawn functions diff --git a/src/swrenderer/things/r_particle.cpp b/src/swrenderer/things/r_particle.cpp index ed961da96..43d680c06 100644 --- a/src/swrenderer/things/r_particle.cpp +++ b/src/swrenderer/things/r_particle.cpp @@ -79,7 +79,7 @@ namespace swrenderer sector_t* heightsec = NULL; double timefrac = r_viewpoint.TicFrac; - if (paused || bglobal.freeze || (sector->Level->flags2 & LEVEL2_FROZEN)) + if (paused || r_viewpoint.ViewLevel->isFrozen()) timefrac = 0.; double ippx = particle->Pos.X + particle->Vel.X * timefrac; diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index a4be48651..acc2229b3 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -448,6 +448,7 @@ class Actor : Thinker native return sin(fb * (180./32)) * 8; } + native bool isFrozen(); virtual native void BeginPlay(); virtual native void Activate(Actor activator); virtual native void Deactivate(Actor activator); diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index c9cee74a8..f0f81d5b6 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -20,7 +20,6 @@ struct _ native // These are the global variables, the struct is only here to av native play uint gameaction; native readonly int gamestate; native readonly TextureID skyflatnum; - native readonly uint8 globalfreeze; native readonly int consoleplayer; native readonly Font smallfont; native readonly Font smallfont2; @@ -46,6 +45,7 @@ struct _ native // These are the global variables, the struct is only here to av native readonly Weapon WP_NOCHANGE; native int LocalViewPitch; native ui readonly LevelLocals currentUILevel; + deprecated("3.8") native readonly bool globalfreeze; } @@ -635,9 +635,9 @@ struct LevelLocals native native readonly Array<@Vertex> Vertexes; native internal Array<@SectorPortal> SectorPortals; - deprecated("3.8") native readonly int time; + native readonly int time; native readonly int maptime; - deprecated("3.8") native readonly int totaltime; + native readonly int totaltime; native readonly int starttime; native readonly int partime; native readonly int sucktime; @@ -648,10 +648,10 @@ struct LevelLocals native native readonly String MapName; native String NextMap; native String NextSecretMap; - deprecated("3.8") native readonly String F1Pic; + native readonly String F1Pic; native readonly int maptype; - deprecated("3.8") native readonly String Music; - deprecated("3.8") native readonly int musicorder; + native readonly String Music; + native readonly int musicorder; native readonly TextureID skytexture1; native readonly TextureID skytexture2; native float skyspeed1; @@ -678,7 +678,7 @@ struct LevelLocals native native readonly bool polygrind; native readonly bool nomonsters; native readonly bool allowrespawn; - native bool frozen; + deprecated("3.8") native bool frozen; native readonly bool infinite_flight; native readonly bool no_dlg_freeze; native readonly bool keepfullinventory; @@ -711,6 +711,8 @@ struct LevelLocals native native void ReplaceTextures(String from, String to, int flags); native vector3, int PickDeathmatchStart(); native vector3, int PickPlayerStart(int pnum, int flags = 0); + native int isFrozen(); + native void setFrozen(bool on); native static clearscope bool IsPointInMap(vector3 p); diff --git a/wadsrc/static/zscript/inventory/powerups.txt b/wadsrc/static/zscript/inventory/powerups.txt index 8327307d0..8cebd0ae0 100644 --- a/wadsrc/static/zscript/inventory/powerups.txt +++ b/wadsrc/static/zscript/inventory/powerups.txt @@ -1531,7 +1531,7 @@ class PowerTimeFreezer : Powerup // Make sure the effect starts and ends on an even tic. if ((Level.maptime & 1) == 0) { - level.frozen = true;; + Level.SetFrozen(true); } else { @@ -1560,7 +1560,7 @@ class PowerTimeFreezer : Powerup // [RH] The "blinking" can't check against EffectTics exactly or it will // never happen, because InitEffect ensures that EffectTics will always // be odd when Level.maptime is even. - Level.frozen = ( EffectTics > 4*32 + Level.SetFrozen ( EffectTics > 4*32 || (( EffectTics > 3*32 && EffectTics <= 4*32 ) && ((EffectTics + 1) & 15) != 0 ) || (( EffectTics > 2*32 && EffectTics <= 3*32 ) && ((EffectTics + 1) & 7) != 0 ) || (( EffectTics > 32 && EffectTics <= 2*32 ) && ((EffectTics + 1) & 3) != 0 ) @@ -1598,7 +1598,7 @@ class PowerTimeFreezer : Powerup } // No, so allow other actors to move about freely once again. - level.frozen = false; + Level.SetFrozen(false); // Also, turn the music back on. S_ResumeSound(false); diff --git a/wadsrc/static/zscript/shared/fastprojectile.txt b/wadsrc/static/zscript/shared/fastprojectile.txt index 9a3a18ad0..da0c003e1 100644 --- a/wadsrc/static/zscript/shared/fastprojectile.txt +++ b/wadsrc/static/zscript/shared/fastprojectile.txt @@ -50,14 +50,8 @@ class FastProjectile : Actor ClearInterpolation(); double oldz = pos.Z; - if (!bNoTimeFreeze) - { - //Added by MC: Freeze mode. - if (globalfreeze || level.Frozen) - { - return; - } - } + if (isFrozen()) + return; // [RH] Ripping is a little different than it was in Hexen FCheckPosition tm; diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt index 67a5592bf..0394fbcbd 100644 --- a/wadsrc/static/zscript/shared/player.txt +++ b/wadsrc/static/zscript/shared/player.txt @@ -2765,7 +2765,7 @@ struct PlayerInfo native play // self is what internally is known as player_t return gamestate == GS_TITLELEVEL || (cheats & CF_TOTALLYFROZEN) || - (mo && mo.Level.frozen && timefreezer == 0); + mo.isFrozen(); } void Uncrouch() diff --git a/wadsrc/static/zscript/shared/player_inventory.txt b/wadsrc/static/zscript/shared/player_inventory.txt index bd01a59a8..ca622707c 100644 --- a/wadsrc/static/zscript/shared/player_inventory.txt +++ b/wadsrc/static/zscript/shared/player_inventory.txt @@ -210,7 +210,7 @@ extend class PlayerPawn { // You can't use items if you're totally frozen return false; } - if ((level.FROZEN) && (player == NULL || player.timefreezer == 0)) + if (isFrozen()) { // Time frozen return false;