swwmgz_m/zscript/swwm_common.zsc
Marisa the Magician 80db58b0d0 Bump zscript ver to 4.14.1, plus a whole lot of stuff.
- Try to get rid of all implicit casts from string to name, color or class.
 - Use FindClass where needed.
 - Used a map in a case where a dictionary was unneeded.
 - Use new bounce flags where needed.
 - Replace Legacy of Rust weapons/ammo.
2025-03-13 14:50:58 +01:00

566 lines
14 KiB
Text

// common code goes here
// extra sound channels for the mod
enum ESWWMGZChannels
{
CHAN_YABLEWIT = 63200, // exception handler
CHAN_DEMOVOICE = 63201, // demolitionist voices
CHAN_FOOTSTEP = 63202, // footstep sounds and others
CHAN_WEAPONEXTRA = 63203, // additional weapon sounds (usually loops)
CHAN_POWERUP = 63204, // powerup sounds
CHAN_POWERUPEXTRA = 63205, // additional powerup sounds
CHAN_JETPACK = 63206, // jetpack sound
CHAN_ITEMEXTRA = 63207, // additional item sounds
CHAN_WEAPONEXTRA2 = 63208, // additional weapon sound slot
CHAN_WEAPONEXTRA3 = 63209, // additional weapon sound slot (again)
CHAN_DAMAGE = 63210, // used for impact/hit sounds
CHAN_AMBEXTRA = 63211, // player ambience when submerged
CHAN_DEMOVOICEAUX = 63212, // extra channel to make oneliner voices louder
CHAN_DEMOVOICEAUX2 = 63213, // how many more channels do I need???
CHAN_DEMOVOICEAUX3 = 63214, // oh god, the loudening
CHAN_FUELREGEN = 63215 // sound of fuel regenerating
};
const FallbackTag = "WHAT IN THE GODDAMN"; // used on tag processing, please don't mind the actual string used)
const MaxBouncePerTic = 40; // maximum simultaneous bounces in one tic for a lightweight actor before we consider it's stuck
// super-minimal tick override that simply advances states when not frozen
Mixin Class SWWMMinimalTick
{
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
}
// + move by velocity
Mixin Class SWWMMinimalMovingTick
{
override void Tick()
{
prev = pos;
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
SetOrigin(level.Vec3Offset(pos,vel),true);
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
}
// + check water level after move
Mixin Class SWWMMinimalMovingWaterTick
{
override void Tick()
{
prev = pos;
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
SetOrigin(level.Vec3Offset(pos,vel),true);
UpdateWaterLevel();
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
}
// absolutely non-interactive actor that is net-safe for clientside use
Class SWWMNonInteractiveActor : Actor
{
Mixin SWWMMinimalTick;
Default
{
+NOINTERACTION;
+NOBLOCKMAP; // needed since we don't use the internal Tick
+SYNCHRONIZED;
+DONTBLAST;
FloatBobPhase 0;
Radius .1;
Height 0;
}
}
// basic "does nothing" actor, used to remove stuff in CheckReplacement
Class SWWMNothing : SWWMNonInteractiveActor
{
States
{
Spawn:
TNT1 A 1;
Stop;
}
}
// mixins for gravity-affected missiles that need correct water behavior
Mixin Class SWWMMissileFix
{
Default
{
+NOFRICTION;
}
override void FallAndSink( double grav, double oldfloorz )
{
if ( bNOGRAVITY || (waterlevel < 1) )
{
Super.FallAndSink(grav,oldfloorz);
return;
}
vel *= .99;
if ( pos.z > floorz ) vel.z -= grav*.01;
}
}
Class SWWMDamageAccumulator : Inventory
{
Actor inflictor, source;
Array<Int> amounts;
int total;
Name type;
bool dontgib;
int flags;
override void DoEffect()
{
Super.DoEffect();
// so many damn safeguards in this
if ( !Owner || (Owner.Health <= 0) )
{
Destroy();
return;
}
int gibhealth = Owner.GetGibHealth();
// お前はもう死んでいる
if ( (Owner.health-total <= gibhealth) && !dontgib )
{
// safeguard for inflictors that have somehow ceased to exist, which apparently STILL CAN HAPPEN
if ( inflictor ) inflictor.bEXTREMEDEATH = true;
else type = 'Extreme';
}
// 何?
foreach ( dmg:amounts )
{
if ( !Owner ) break;
Owner.DamageMobj(inflictor,source,dmg,type,DMG_THRUSTLESS|flags);
}
// clean up
if ( inflictor ) inflictor.bEXTREMEDEATH = inflictor.default.bEXTREMEDEATH;
Destroy();
}
static void Accumulate( Actor victim, int amount, Actor inflictor, Actor source, Name type, bool dontgib = false, int flags = 0 )
{
if ( !victim ) return;
SWWMDamageAccumulator match = SWWMDamageAccumulator(victim.FindInventory('SWWMDamageAccumulator'));
if ( !match )
{
match = SWWMDamageAccumulator(Spawn('SWWMDamageAccumulator'));
match.AttachToOwner(victim);
}
match.amounts.Push(amount);
match.total += amount;
match.inflictor = inflictor;
match.source = source;
match.type = type;
match.dontgib = dontgib;
match.flags = flags;
}
static clearscope int GetAmount( Actor victim )
{
let ti = ThinkerIterator.Create('SWWMDamageAccumulator',STAT_USER);
SWWMDamageAccumulator match = SWWMDamageAccumulator(victim.FindInventory('SWWMDamageAccumulator'));
if ( match )
{
if ( match.source && match.source.FindInventory('AngeryPower') )
return (match.total>85899345)?int.max:(match.total*25);
return match.total;
}
return 0;
}
default
{
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNCLEARABLE;
}
}
// Track last damage source to blame fall damage on
Class SWWMWhoPushedMe : Inventory
{
Actor instigator;
int killtimer;
static void SetInstigator( Actor b, Actor whomst )
{
if ( !b || !whomst ) return;
SWWMWhoPushedMe ffd = SWWMWhoPushedMe(b.FindInventory('SWWMWhoPushedMe'));
if ( ffd )
{
ffd.instigator = whomst;
ffd.killtimer = 0; // cancel kill timer
return;
}
ffd = SWWMWhoPushedMe(Spawn('SWWMWhoPushedMe'));
ffd.AttachToOwner(b);
ffd.instigator = whomst;
}
static Actor RecallInstigator( Actor b )
{
if ( !b ) return null;
SWWMWhoPushedMe ffd = SWWMWhoPushedMe(b.FindInventory('SWWMWhoPushedMe'));
if ( ffd )
{
Actor whomst = ffd.instigator;
ffd.Destroy();
return whomst;
}
return null;
}
override void OwnerDied()
{
Super.OwnerDied();
if ( killtimer > 0 ) return;
killtimer = 5;
}
override void DoEffect()
{
Super.DoEffect();
if ( killtimer <= 0 ) return;
killtimer--;
if ( killtimer > 0 ) return;
Destroy();
}
override void ModifyDamage( int damage, Name damageType, int &newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( !passive || (damageType != 'Falling') || (killtimer > 0) ) return;
killtimer = 5;
}
default
{
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNCLEARABLE;
}
}
Class SWWMFlyTracker : Inventory
{
Actor instigator;
Vector3 startpos, curpos;
double maxdist;
int gracepd;
static void Track( Actor b, Actor whomst )
{
if ( !b || !whomst ) return;
SWWMFlyTracker ffd = SWWMFlyTracker(b.FindInventory('SWWMFlyTracker'));
if ( ffd )
{
ffd.instigator = whomst;
return;
}
ffd = SWWMFlyTracker(Spawn('SWWMFlyTracker'));
ffd.AttachToOwner(b);
ffd.instigator = whomst;
ffd.curpos = ffd.startpos = b.pos;
ffd.maxdist = 0;
}
override void DoEffect()
{
maxdist = max(maxdist,level.Vec3Diff(startpos,curpos).length());
if ( !Owner || Owner.bFLOAT || Owner.bNOGRAVITY || (Owner.waterlevel > 1) || (Owner.pos.z <= Owner.floorz) || !Owner.TestMobjZ(false) )
{
gracepd++;
if ( gracepd < 10 ) return;
if ( instigator ) SWWMUtility.AchievementProgress("flight",int(maxdist),instigator.player);
Destroy();
return;
}
gracepd = 0;
curpos = Owner.pos;
}
default
{
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNCLEARABLE;
}
}
// very lightweight combat tracker, more Souls-y
Class SWWMQuickCombatTracker : Inventory
{
int lifespan, lasthealth, maxhealth;
int laghealth[10];
int cummdamage, cummspan, cummflash; // please do not misread
int fadein; // used for the hud
double lvheight; // height while alive (used by the hud for positioning)
SmoothDynamicValueInterpolator intp;
SmoothLinearValueInterpolator intpl;
PlayerInfo myplayer; // for multiplayer compatibility (this breaks inventory logic, but alas...)
String mytag;
bool PMHack;
void UpdateTag( SWWMHandler hnd )
{
if ( Owner && (Owner.player || Owner.bISMONSTER || (Owner is 'BossBrain') || (Owner is 'SWWMHangingKeen') || (Owner is 'PlayerPawn')) )
{
String realtag = swwm_funtags?SWWMUtility.GetFunTag(hnd,Owner,FallbackTag):Owner.GetTag(FallbackTag);
if ( realtag == FallbackTag )
{
realtag = Owner.GetClassName();
SWWMUtility.BeautifyClassName(realtag);
}
mytag = Owner.player?(Owner.player.mo!=Owner)?multiplayer?String.Format(StringTable.Localize("$FN_VOODOO"),Owner.player.GetUserName()):StringTable.Localize("$FN_VOODOO_NP"):Owner.player.GetUserName():(Owner is 'PlayerPawn')?StringTable.Localize("$FN_VOODOO_NP"):realtag;
}
else mytag = "";
}
override void AttachToOwner( Actor other )
{
// GROSS HACK TO ATTACH TO VOODOO DOLLS
if ( other.player && (other.player.mo != other) )
{
BecomeItem();
Owner = other;
inv = Owner.inv;
Owner.inv = self;
// we unfortunately can't access this, but hopefully it shouldn't be necessary
//InventoryID = Owner.InventoryID++;
return;
}
Super.AttachToOwner(other);
}
override bool HandlePickup( Inventory item )
{
// force update tag if Grace of Lilith has glitched our owner
if ( item.GetClassName() == 'CCards_Token_Glitched' )
{
let hnd = SWWMHandler(EventHandler.Find('SWWMHandler'));
UpdateTag(hnd);
}
return Super.HandlePickup(item);
}
static SWWMQuickCombatTracker Update( SWWMHandler hnd, PlayerInfo p, Actor target, int damage = 0 )
{
// no-damage entities get no healthbars
if ( target.bNODAMAGE ) return null;
// also don't give healthbars to moths
if ( target is 'LampMoth' ) return null;
// don't give them to boss brains unless visible
if ( (target is 'BossBrain') && !p.Camera.CheckSight(target,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return null;
SWWMQuickCombatTracker t = null;
for ( Inventory i=target.inv; i; i=i.inv )
{
if ( !(i is 'SWWMQuickCombatTracker') ) continue;
let tt = SWWMQuickCombatTracker(i);
if ( tt.myplayer != p ) continue;
t = tt;
break;
}
// must exclude players (unless they're voodoo dolls)
// this is mainly for deathmatch so players you hit don't become easy to track when out of view
bool realmonster = target.bISMONSTER&&(!target.player||(target.player&&(target.player.mo!=target)));
if ( t )
{
// re-fade in
if ( t.lifespan < 20 ) t.fadein = t.lifespan/4;
t.lifespan = max(t.lifespan,realmonster?((damage>0)?700:70):((damage>0)?140:35));
if ( damage > 0 )
{
t.cummdamage += damage;
t.cummspan = realmonster?120:30;
t.cummflash = 15;
}
return t;
}
t = SWWMQuickCombatTracker(Spawn('SWWMQuickCombatTracker'));
t.myplayer = p;
t.AttachToOwner(target);
// players always use SpawnHealth
if ( target.player ) t.maxhealth = target.SpawnHealth();
else t.maxhealth = max(target.health,target.SpawnHealth());
t.lasthealth = target.health;
int prevhealth = min(t.maxhealth,target.health+damage); // guessed health before damage was dealt
for ( int i=0; i<10; i++ ) t.laghealth[i] = prevhealth;
if ( damage > 0 )
{
t.cummdamage = damage;
t.cummspan = realmonster?120:30;
t.cummflash = 15;
}
t.intp = SmoothDynamicValueInterpolator.Create(t.lasthealth,.5);
t.intpl = SmoothLinearValueInterpolator.Create(t.laghealth[9],max(1,t.maxhealth/50));
t.lifespan = realmonster?((damage>0)?700:70):((damage>0)?140:35);
t.fadein = 0;
t.lvheight = target.Height;
t.UpdateTag(hnd);
// hack for some Project Malice void monsters that regularly change tags
switch ( target.GetClassName() )
{
case 'PM_EntropicAvatar':
case 'PM_Broken':
case 'PM_VoidFloater':
t.PMHack = true;
break;
}
return t;
}
// expiration, and interpolator updates
override void DoEffect()
{
Super.DoEffect();
// immediately expire if the owner is gone or has morphed
if ( !Owner || Owner.bUnmorphed )
{
Destroy();
return;
}
if ( PMHack && !Owner.isFrozen() )
{
let hnd = SWWMHandler(EventHandler.Find('SWWMHandler'));
UpdateTag(hnd);
}
// cap lifespan if owner is dead
if ( Owner.Health <= 0 ) lifespan = min(lifespan,35);
else lvheight = Owner.Height;
if ( cummflash > 0 ) cummflash--;
if ( cummspan > 0 )
{
cummspan--;
if ( cummspan <= 0 ) cummdamage = 0;
}
if ( Owner.Health > lasthealth )
{
if ( !Owner.player && (Owner.Health > maxhealth) )
maxhealth = Owner.Health;
for ( int i=9; i>0; i-- )
laghealth[i] = Owner.Health;
}
laghealth[0] = lasthealth = Owner.Health;
intp.Update(lasthealth);
intpl.Update(laghealth[9]);
for ( int i=9; i>0; i-- )
laghealth[i] = laghealth[i-1];
if ( fadein < 5 ) fadein++;
lifespan = max(0,lifespan-1);
// only fully expire if owner dies
// (this is so we can preserve our current fun tag)
if ( (lifespan <= 0) && (Owner.Health <= 0) )
Destroy();
}
override void PreTravelled()
{
// expire immediately when switching maps
Destroy();
}
default
{
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNCLEARABLE;
}
}
// fractic-compatible interpolators, with double value
Class SmoothLinearValueInterpolator
{
private double val, oldval, diff;
static SmoothLinearValueInterpolator Create( double val, double diff )
{
let v = new('SmoothLinearValueInterpolator');
v.oldval = v.val = val;
v.diff = diff;
return v;
}
void Reset( double newval )
{
oldval = val = newval;
}
void Update( double newval )
{
oldval = val;
if ( abs(newval-val) < diff ) val = newval;
else if ( val > newval ) val = max(newval,val-diff);
else val = min(newval,val+diff);
}
double GetValue( double fractic = 1. )
{
return (val~==oldval)?val:SWWMUtility.Lerp(oldval,val,fractic);
}
}
Class SmoothDynamicValueInterpolator
{
private double val, oldval, factor, mindiff, maxdiff;
static SmoothDynamicValueInterpolator Create( double val, double factor, double mindiff = 1., double maxdiff = 0. )
{
let v = new('SmoothDynamicValueInterpolator');
v.oldval = v.val = val;
v.factor = factor;
v.mindiff = mindiff;
v.maxdiff = maxdiff;
return v;
}
void Reset( double newval )
{
oldval = val = newval;
}
void Update( double newval )
{
oldval = val;
if ( abs(newval-val) < mindiff ) val = newval;
else
{
double diff = (maxdiff>0.)?min(abs(newval-val)*factor,maxdiff):(abs(newval-val)*factor);
if ( val > newval ) val = max(newval,val-diff);
else val = min(newval,val+diff);
}
}
double GetValue( double fractic = 1. )
{
return (val~==oldval)?val:SWWMUtility.Lerp(oldval,val,fractic);
}
}