546 lines
14 KiB
Text
546 lines
14 KiB
Text
// common code goes here
|
|
|
|
// extra sound channels for the mod
|
|
enum ESWWMGZChannels
|
|
{
|
|
CHAN_YOUDONEFUCKEDUP = 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 = "AWESOME IT'S PENIS"; // 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';
|
|
}
|
|
// make sure accumulation isn't reentrant
|
|
if ( inflictor && (inflictor is 'EvisceratorChunk') ) inflictor.bAMBUSH = true;
|
|
// 何?
|
|
foreach ( dmg:amounts )
|
|
{
|
|
if ( !Owner ) break;
|
|
Owner.DamageMobj(inflictor,source,dmg,type,DMG_THRUSTLESS|flags);
|
|
}
|
|
// clean up
|
|
if ( inflictor )
|
|
{
|
|
if ( inflictor is 'EvisceratorChunk' ) inflictor.bAMBUSH = false;
|
|
inflictor.bEXTREMEDEATH = false;
|
|
}
|
|
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, out 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;
|
|
|
|
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')&&(!Owner.player))?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);
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
// 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);
|
|
}
|
|
}
|