diff --git a/src/g_level.cpp b/src/g_level.cpp index 17d1366ea..8f0652d56 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1583,6 +1583,7 @@ int FLevelLocals::FinishTravel () pawn->ceilingpic = pawndup->ceilingpic; pawn->Floorclip = pawndup->Floorclip; pawn->waterlevel = pawndup->waterlevel; + pawn->waterdepth = pawndup->waterdepth; } else if (failnum == 0) // In the failure case this may run into some undefined data. { diff --git a/src/namedef_custom.h b/src/namedef_custom.h index 6f7bcbc78..e94502c3c 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -497,6 +497,7 @@ xx(Special) xx(TID) xx(TIDtoHate) xx(WaterLevel) +xx(WaterDepth) xx(MomX) xx(MomY) xx(MomZ) diff --git a/src/playsim/actor.h b/src/playsim/actor.h index d3bfbfced..3a39a5d03 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -773,6 +773,10 @@ public: virtual void Touch(AActor *toucher); void CallTouch(AActor *toucher); + // Apply gravity and/or make actor sink in water. + virtual void FallAndSink(double grav, double oldfloorz); + void CallFallAndSink(double grav, double oldfloorz); + // Centaurs and ettins squeal when electrocuted, poisoned, or "holy"-ed // Made a metadata property so no longer virtual void Howl (); @@ -1137,6 +1141,7 @@ public: AActor *inext, **iprev;// Links to other mobjs in same bucket TObjPtr goal; // Monster's goal if not chasing anything int waterlevel; // 0=none, 1=feet, 2=waist, 3=eyes + double waterdepth; // Stores how deep into water you are, in map units uint8_t boomwaterlevel; // splash information for non-swimmable water sectors uint8_t MinMissileChance;// [RH] If a random # is > than this, then missile attack. int8_t LastLookPlayerNumber;// Player number last looked for (if TIDtoHate == 0) @@ -1275,6 +1280,7 @@ public: bool IsMapActor(); int GetTics(FState * newstate); bool SetState (FState *newstate, bool nofunction=false); + int UpdateWaterDepth(bool splash); virtual void SplashCheck(); virtual bool UpdateWaterLevel (bool splash=true); bool isFast(); diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 67073da00..afc8c7ed4 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -2397,84 +2397,7 @@ void P_ZMovement (AActor *mo, double oldfloorz) mo->AddZ(mo->Vel.Z); -// -// apply gravity -// - if (mo->Z() > mo->floorz && !(mo->flags & MF_NOGRAVITY)) - { - double startvelz = mo->Vel.Z; - - if (mo->waterlevel == 0 || (mo->player && - !(mo->player->cmd.ucmd.forwardmove | mo->player->cmd.ucmd.sidemove))) - { - // [RH] Double gravity only if running off a ledge. Coming down from - // an upward thrust (e.g. a jump) should not double it. - if (mo->Vel.Z == 0 && oldfloorz > mo->floorz && mo->Z() == oldfloorz) - { - mo->Vel.Z -= grav + grav; - } - else - { - mo->Vel.Z -= grav; - } - } - if (mo->player == NULL) - { - if (mo->waterlevel >= 1) - { - double sinkspeed; - - if ((mo->flags & MF_SPECIAL) && !(mo->flags3 & MF3_ISMONSTER)) - { // Pickup items don't sink if placed and drop slowly if dropped - sinkspeed = (mo->flags & MF_DROPPED) ? -WATER_SINK_SPEED / 8 : 0; - } - else - { - sinkspeed = -WATER_SINK_SPEED; - - // If it's not a player, scale sinkspeed by its mass, with - // 100 being equivalent to a player. - if (mo->player == NULL) - { - sinkspeed = sinkspeed * clamp(mo->Mass, 1, 4000) / 100; - } - } - if (mo->Vel.Z < sinkspeed) - { // Dropping too fast, so slow down toward sinkspeed. - mo->Vel.Z -= max(sinkspeed*2, -8.); - if (mo->Vel.Z > sinkspeed) - { - mo->Vel.Z = sinkspeed; - } - } - else if (mo->Vel.Z > sinkspeed) - { // Dropping too slow/going up, so trend toward sinkspeed. - mo->Vel.Z = startvelz + max(sinkspeed/3, -8.); - if (mo->Vel.Z < sinkspeed) - { - mo->Vel.Z = sinkspeed; - } - } - } - } - else - { - if (mo->waterlevel > 1) - { - double sinkspeed = -WATER_SINK_SPEED; - - if (mo->Vel.Z < sinkspeed) - { - mo->Vel.Z = (startvelz < sinkspeed) ? startvelz : sinkspeed; - } - else - { - mo->Vel.Z = startvelz + ((mo->Vel.Z - startvelz) * - (mo->waterlevel == 1 ? WATER_SINK_SMALL_FACTOR : WATER_SINK_FACTOR)); - } - } - } - } + mo->CallFallAndSink(grav, oldfloorz); // Hexen compatibility handling for floatbobbing. Ugh... // Hexen yanked all items to the floor, except those being spawned at map start in the air. @@ -2808,7 +2731,112 @@ static void PlayerLandedOnThing (AActor *mo, AActor *onmobj) // mo->player->centering = true; } +//========================================================================== +// +// AActor :: FallAndSink +// +//========================================================================== +void AActor::FallAndSink(double grav, double oldfloorz) +{ + if (Z() > floorz && !(flags & MF_NOGRAVITY)) + { + double startvelz = Vel.Z; + + if (waterlevel == 0 || (player && + !(player->cmd.ucmd.forwardmove | player->cmd.ucmd.sidemove))) + { + // [RH] Double gravity only if running off a ledge. Coming down from + // an upward thrust (e.g. a jump) should not double it. + if (Vel.Z == 0 && oldfloorz > floorz && Z() == oldfloorz) + { + Vel.Z -= grav + grav; + } + else + { + Vel.Z -= grav; + } + } + if (player == NULL) + { + if (waterlevel >= 1) + { + double sinkspeed; + + if ((flags & MF_SPECIAL) && !(flags3 & MF3_ISMONSTER)) + { // Pickup items don't sink if placed and drop slowly if dropped + sinkspeed = (flags & MF_DROPPED) ? -WATER_SINK_SPEED / 8 : 0; + } + else + { + sinkspeed = -WATER_SINK_SPEED; + + // If it's not a player, scale sinkspeed by its mass, with + // 100 being equivalent to a player. + if (player == NULL) + { + sinkspeed = sinkspeed * clamp(Mass, 1, 4000) / 100; + } + } + if (Vel.Z < sinkspeed) + { // Dropping too fast, so slow down toward sinkspeed. + Vel.Z -= max(sinkspeed * 2, -8.); + if (Vel.Z > sinkspeed) + { + Vel.Z = sinkspeed; + } + } + else if (Vel.Z > sinkspeed) + { // Dropping too slow/going up, so trend toward sinkspeed. + Vel.Z = startvelz + max(sinkspeed / 3, -8.); + if (Vel.Z < sinkspeed) + { + Vel.Z = sinkspeed; + } + } + } + } + else + { + if (waterlevel > 1) + { + double sinkspeed = -WATER_SINK_SPEED; + + if (Vel.Z < sinkspeed) + { + Vel.Z = (startvelz < sinkspeed) ? startvelz : sinkspeed; + } + else + { + Vel.Z = startvelz + ((Vel.Z - startvelz) * + (waterlevel == 1 ? WATER_SINK_SMALL_FACTOR : WATER_SINK_FACTOR)); + } + } + } + } +} + +DEFINE_ACTION_FUNCTION(AActor, FallAndSink) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_FLOAT(grav); + PARAM_FLOAT(oldfloorz); + self->FallAndSink(grav, oldfloorz); + return 0; +} + +void AActor::CallFallAndSink(double grav, double oldfloorz) +{ + IFVIRTUAL(AActor, FallAndSink) + { + VMValue params[3] = { (DObject*)this, grav, oldfloorz }; + VMCall(func, params, 3, nullptr, 0); + } + else + { + FallAndSink(grav, oldfloorz); + } +} // // P_NightmareRespawn @@ -4233,27 +4261,32 @@ void AActor::CheckSectorTransition(sector_t *oldsec) //========================================================================== // -// AActor::SplashCheck +// AActor::UpdateWaterDepth // -// Returns true if actor should splash +// Updates the actor's current waterlevel and waterdepth. +// Consolidates common code in UpdateWaterLevel and SplashCheck. +// +// Returns the floor height used for the depth check, or -FLT_MAX +// if the actor wasn't in a sector. // //========================================================================== -void AActor::SplashCheck() +int AActor::UpdateWaterDepth(bool splash) { double fh = -FLT_MAX; bool reset = false; waterlevel = 0; + waterdepth = 0; if (Sector == NULL) { - return; + return fh; } if (Sector->MoreFlags & SECMF_UNDERWATER) // intentionally not SECMF_UNDERWATERMASK { - waterlevel = 3; + waterdepth = Height; } else { @@ -4261,28 +4294,16 @@ void AActor::SplashCheck() if (hsec != NULL) { fh = hsec->floorplane.ZatPoint(this); - //if (hsec->MoreFlags & SECMF_UNDERWATERMASK) // also check Boom-style non-swimmable sectors + + // splash checks also check Boom-style non-swimmable sectors + // as well as non-solid, visible 3D floors (below) + if (splash || hsec->MoreFlags & SECMF_UNDERWATERMASK) { - if (Z() < fh) + waterdepth = fh - Z(); + + if (waterdepth <= 0 && !(hsec->MoreFlags & SECMF_FAKEFLOORONLY) && (Top() > hsec->ceilingplane.ZatPoint(this))) { - waterlevel = 1; - if (Center() < fh) - { - waterlevel = 2; - if ((player && Z() + player->viewheight <= fh) || - (Top() <= fh)) - { - waterlevel = 3; - } - } - } - else if (!(hsec->MoreFlags & SECMF_FAKEFLOORONLY) && (Top() > hsec->ceilingplane.ZatPoint(this))) - { - waterlevel = 3; - } - else - { - waterlevel = 0; + waterdepth = Height; } } } @@ -4295,32 +4316,56 @@ void AActor::SplashCheck() if (rover->flags & FF_SOLID) continue; bool reset = !(rover->flags & FF_SWIMMABLE); - if (reset && rover->alpha == 0) continue; + if (splash) { reset &= rover->alpha == 0; } + if (reset) continue; + double ff_bottom = rover->bottom.plane->ZatPoint(this); double ff_top = rover->top.plane->ZatPoint(this); - if (ff_top <= Z() || ff_bottom > (Center())) continue; + if (ff_top <= Z() || ff_bottom > Center()) continue; fh = ff_top; - if (Z() < fh) - { - waterlevel = 1; - if (Center() < fh) - { - waterlevel = 2; - if ((player && Z() + player->viewheight <= fh) || - (Top() <= fh)) - { - waterlevel = 3; - } - } - } - + waterdepth = ff_top - Z(); break; } } } + if (waterdepth < 0) { waterdepth = 0; } + + if (waterdepth > (Height / 2)) + { + // When noclipping around and going from low to high sector, your view height + // can go negative, which is why this is nested inside here + if ((player && (waterdepth >= player->viewheight)) || (waterdepth >= Height)) + { + waterlevel = 3; + } + else + { + waterlevel = 2; + } + } + else if (waterdepth > 0) + { + waterlevel = 1; + } + + return fh; +} + +//========================================================================== +// +// AActor::SplashCheck +// +// Returns true if actor should splash +// +//========================================================================== + +void AActor::SplashCheck() +{ + double fh = UpdateWaterDepth(true); + // some additional checks to make deep sectors like Boom's splash without setting // the water flags. if (boomwaterlevel == 0 && waterlevel != 0) @@ -4341,106 +4386,36 @@ void AActor::SplashCheck() bool AActor::UpdateWaterLevel(bool dosplash) { - int oldlevel = waterlevel; - if (dosplash) SplashCheck(); - double fh = -FLT_MAX; - bool reset = false; + int oldlevel = waterlevel; + UpdateWaterDepth(false); - waterlevel = 0; + // Play surfacing and diving sounds, as appropriate. + // + // (used to be that this code was wrapped around a "Sector != nullptr" check, + // but actors should always be within a sector, and besides, this is just + // sound stuff) - if (Sector != nullptr) + if (player != nullptr) { - if (Sector->MoreFlags & SECMF_UNDERWATER) // intentionally not SECMF_UNDERWATERMASK + if (oldlevel < 3 && waterlevel == 3) { - waterlevel = 3; + // Our head just went under. + S_Sound(this, CHAN_VOICE, 0, "*dive", 1, ATTN_NORM); } - else + else if (oldlevel == 3 && waterlevel < 3) { - const sector_t *hsec = Sector->GetHeightSec(); - if (hsec != NULL) + // Our head just came up. + if (player->air_finished > Level->maptime) { - fh = hsec->floorplane.ZatPoint(this); - if (hsec->MoreFlags & SECMF_UNDERWATERMASK) // also check Boom-style non-swimmable sectors - { - if (Z() < fh) - { - waterlevel = 1; - if (Center() < fh) - { - waterlevel = 2; - if ((player && Z() + player->viewheight <= fh) || - (Top() <= fh)) - { - waterlevel = 3; - } - } - } - else if (!(hsec->MoreFlags & SECMF_FAKEFLOORONLY) && (Top() > hsec->ceilingplane.ZatPoint(this))) - { - waterlevel = 3; - } - else - { - waterlevel = 0; - } - } - } - else - { - // Check 3D floors as well! - for (auto rover : Sector->e->XFloor.ffloors) - { - if (!(rover->flags & FF_EXISTS)) continue; - if (rover->flags & FF_SOLID) continue; - if (!(rover->flags & FF_SWIMMABLE)) continue; - - double ff_bottom = rover->bottom.plane->ZatPoint(this); - double ff_top = rover->top.plane->ZatPoint(this); - - if (ff_top <= Z() || ff_bottom > (Center())) continue; - - fh = ff_top; - if (Z() < fh) - { - waterlevel = 1; - if (Center() < fh) - { - waterlevel = 2; - if ((player && Z() + player->viewheight <= fh) || - (Top() <= fh)) - { - waterlevel = 3; - } - } - } - - break; - } - } - } - - // Play surfacing and diving sounds, as appropriate. - if (player != nullptr) - { - if (oldlevel < 3 && waterlevel == 3) - { - // Our head just went under. - S_Sound(this, CHAN_VOICE, 0, "*dive", 1, ATTN_NORM); - } - else if (oldlevel == 3 && waterlevel < 3) - { - // Our head just came up. - if (player->air_finished > Level->maptime) - { - // We hadn't run out of air yet. - S_Sound(this, CHAN_VOICE, 0, "*surface", 1, ATTN_NORM); - } - // If we were running out of air, then ResetAirSupply() will play *gasp. + // We hadn't run out of air yet. + S_Sound(this, CHAN_VOICE, 0, "*surface", 1, ATTN_NORM); } + // If we were running out of air, then ResetAirSupply() will play *gasp. } } + return false; // we did the splash ourselves } diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 37a9a0794..13a516184 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -1930,6 +1930,7 @@ DEFINE_FIELD(AActor, special) DEFINE_FIELD(AActor, tid) DEFINE_FIELD(AActor, TIDtoHate) DEFINE_FIELD(AActor, waterlevel) +DEFINE_FIELD(AActor, waterdepth) DEFINE_FIELD(AActor, Score) DEFINE_FIELD(AActor, accuracy) DEFINE_FIELD(AActor, stamina) diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 6caa7de6f..0d277ae4f 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -167,6 +167,7 @@ class Actor : Thinker native native readonly int TID; native readonly int TIDtoHate; native readonly int WaterLevel; + native readonly double WaterDepth; native int Score; native int Accuracy; native int Stamina; @@ -484,6 +485,7 @@ class Actor : Thinker native virtual native void Die(Actor source, Actor inflictor, int dmgflags = 0, Name MeansOfDeath = 'none'); virtual native bool Slam(Actor victim); virtual native void Touch(Actor toucher); + virtual native void FallAndSink(double grav, double oldfloorz); private native void Substitute(Actor replacement); native ui void DisplayNameTag();