393 lines
10 KiB
Text
393 lines
10 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
|
|
|
|
// basic "does nothing" actor, used to remove stuff in CheckReplacement
|
|
Class SWWMNothing : Actor
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
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;
|
|
// 何?
|
|
for ( int i=0; i<amounts.Size(); i++ )
|
|
{
|
|
if ( !Owner ) break;
|
|
Owner.DamageMobj(inflictor,source,amounts[i],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;
|
|
|
|
static void SetInstigator( Actor b, Actor whomst )
|
|
{
|
|
if ( !b || !whomst ) return;
|
|
SWWMWhoPushedMe ffd = SWWMWhoPushedMe(b.FindInventory("SWWMWhoPushedMe"));
|
|
if ( ffd )
|
|
{
|
|
ffd.instigator = whomst;
|
|
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;
|
|
}
|
|
|
|
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 = "";
|
|
}
|
|
|
|
static SWWMQuickCombatTracker Update( SWWMHandler hnd, PlayerInfo p, Actor target, int damage = 0 )
|
|
{
|
|
if ( target.bNODAMAGE ) return null; // no-damage entities get no healthbars
|
|
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;
|
|
}
|
|
if ( t )
|
|
{
|
|
// re-fade in
|
|
if ( t.lifespan < 20 ) t.fadein = t.lifespan/4;
|
|
t.lifespan = max(t.lifespan,target.bISMONSTER?((damage>0)?700:70):((damage>0)?140:35));
|
|
if ( damage > 0 )
|
|
{
|
|
t.cummdamage += damage;
|
|
t.cummspan = target.bISMONSTER?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 = target.bISMONSTER?120:30;
|
|
t.cummflash = 15;
|
|
}
|
|
t.intp = SmoothDynamicValueInterpolator.Create(t.lasthealth,.5,1,100);
|
|
t.intpl = SmoothLinearValueInterpolator.Create(t.laghealth[9],max(1,t.maxhealth/20));
|
|
t.lifespan = target.bISMONSTER?((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--;
|
|
if ( lifespan <= 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, double maxdiff )
|
|
{
|
|
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 = min(abs(newval-val)*factor,maxdiff);
|
|
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);
|
|
}
|
|
}
|