Remove dead code. Tweak how enemy flight distance is calculated. Rename flight achievement to something more fitting.
441 lines
9.9 KiB
Text
441 lines
9.9 KiB
Text
// various stat tracking thinkers and others
|
|
|
|
// Korax instakill handler
|
|
Class UglyBoyGetsFuckedUp : Thinker
|
|
{
|
|
bool wedone;
|
|
|
|
override void Tick()
|
|
{
|
|
if ( wedone ) return;
|
|
if ( level.killed_monsters < level.total_monsters )
|
|
{
|
|
// stop portal door
|
|
int sidx = level.CreateSectorTagIterator(145).Next();
|
|
if ( sidx == -1 ) return;
|
|
Sector door = level.Sectors[sidx];
|
|
let ti = ThinkerIterator.Create("SectorEffect");
|
|
SectorEffect se;
|
|
while ( se = SectorEffect(ti.Next()) )
|
|
{
|
|
if ( se.GetSector() != door ) continue;
|
|
se.Destroy();
|
|
door.StopSoundSequence(CHAN_VOICE);
|
|
}
|
|
return;
|
|
}
|
|
wedone = true;
|
|
level.ExecuteSpecial(Door_Open,null,null,false,145,8);
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
// Track last damage source to blame fall damage on
|
|
Class SWWMWhoPushedMe : Thinker
|
|
{
|
|
Actor tracked, instigator;
|
|
|
|
static void SetInstigator( Actor b, Actor whomst )
|
|
{
|
|
if ( !b || !whomst ) return;
|
|
let ti = ThinkerIterator.Create("SWWMWhoPushedMe",STAT_INFO);
|
|
SWWMWhoPushedMe ffd;
|
|
while ( ffd = SWWMWhoPushedMe(ti.Next()) )
|
|
{
|
|
if ( ffd.tracked != b ) continue;
|
|
ffd.instigator = whomst;
|
|
return;
|
|
}
|
|
ffd = new("SWWMWhoPushedMe");
|
|
ffd.ChangeStatNum(STAT_INFO);
|
|
ffd.tracked = b;
|
|
ffd.instigator = whomst;
|
|
}
|
|
|
|
static Actor RecallInstigator( Actor b )
|
|
{
|
|
if ( !b ) return null;
|
|
let ti = ThinkerIterator.Create("SWWMWhoPushedMe",STAT_INFO);
|
|
SWWMWhoPushedMe ffd;
|
|
while ( ffd = SWWMWhoPushedMe(ti.Next()) )
|
|
{
|
|
if ( ffd.tracked != b ) continue;
|
|
Actor whomst = ffd.instigator;
|
|
ffd.Destroy();
|
|
return whomst;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Class SWWMDamageAccumulator : Thinker
|
|
{
|
|
Actor victim, inflictor, source;
|
|
Array<Int> amounts;
|
|
int total;
|
|
Name type;
|
|
bool dontgib;
|
|
int flags;
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
// so many damn safeguards in this
|
|
if ( !victim || (victim.Health <= 0) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
int gibhealth = victim.GetGibHealth();
|
|
// お前はもう死んでいる
|
|
if ( (victim.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 ( !victim ) break;
|
|
victim.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;
|
|
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
|
|
SWWMDamageAccumulator a, match = null;
|
|
while ( a = SWWMDamageAccumulator(ti.Next()) )
|
|
{
|
|
if ( a.victim != victim ) continue;
|
|
match = a;
|
|
break;
|
|
}
|
|
if ( !match )
|
|
{
|
|
match = new("SWWMDamageAccumulator");
|
|
match.ChangeStatNum(STAT_USER);
|
|
match.victim = victim;
|
|
match.amounts.Clear();
|
|
}
|
|
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 a, match = null;
|
|
while ( a = SWWMDamageAccumulator(ti.Next()) )
|
|
{
|
|
if ( a.victim != victim ) continue;
|
|
return a.total;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// prevents floors/ceilings from ever moving again, as they're "broken crushers"
|
|
// optional "instant" parameter is used by wall busting, no crusher malfunction animation will play
|
|
Class SWWMCrusherBroken : Thinker
|
|
{
|
|
Sector fsec, csec;
|
|
double diffh;
|
|
int fphase, cphase;
|
|
int ftics, ctics;
|
|
|
|
static void Create( Sector f, Sector c, double diffh, bool instant = false )
|
|
{
|
|
if ( !f && !c ) return;
|
|
let ti = ThinkerIterator.Create("SWWMCrusherBroken",STAT_USER);
|
|
SWWMCrusherBroken cb;
|
|
while ( cb = SWWMCrusherBroken(ti.Next()) )
|
|
{
|
|
if ( (cb.fsec == f) && (cb.csec == c) )
|
|
{
|
|
if ( instant )
|
|
{
|
|
// force it to be instant
|
|
if ( f ) cb.fphase = 3;
|
|
if ( c ) cb.cphase = 3;
|
|
}
|
|
return; // we already have this
|
|
}
|
|
if ( cb.fsec && (cb.fsec == f) )
|
|
{
|
|
cb.Destroy(); // we override this one
|
|
continue;
|
|
}
|
|
if ( cb.csec && (cb.csec == c) )
|
|
{
|
|
cb.Destroy(); // we override this one
|
|
continue;
|
|
}
|
|
}
|
|
cb = new("SWWMCrusherBroken");
|
|
cb.fsec = f;
|
|
cb.csec = c;
|
|
cb.ChangeStatNum(STAT_USER);
|
|
cb.diffh = diffh;
|
|
if ( f && f.floordata ) f.floordata.Destroy();
|
|
if ( c && c.ceilingdata ) c.ceilingdata.Destroy();
|
|
if ( instant )
|
|
{
|
|
if ( f ) cb.fphase = 3;
|
|
if ( c ) cb.cphase = 3;
|
|
}
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( fsec )
|
|
{
|
|
if ( cphase <= 0 )
|
|
{
|
|
if ( level.CreateFloor(fsec,Floor.floorLowerByValue,null,16.,diffh*.4) )
|
|
{
|
|
ftics = int(diffh*.4/16.)+40;
|
|
fphase = 1;
|
|
}
|
|
}
|
|
else if ( fphase == 1 )
|
|
{
|
|
ftics--;
|
|
if ( (ftics <= 0) && level.CreateFloor(fsec,Floor.floorLowerByValue,null,1.,diffh*.6) )
|
|
{
|
|
ftics = int(diffh*.6)+8;
|
|
fphase = 2;
|
|
}
|
|
}
|
|
else if ( fphase == 2 )
|
|
{
|
|
ftics--;
|
|
if ( ftics <= 0 ) fphase = 3;
|
|
}
|
|
else if ( (fphase >= 3) && fsec.floordata )
|
|
{
|
|
fsec.floordata.Destroy();
|
|
fsec.StopSoundSequence(CHAN_WEAPON);
|
|
}
|
|
}
|
|
if ( csec )
|
|
{
|
|
if ( cphase <= 0 )
|
|
{
|
|
if ( level.CreateCeiling(csec,Ceiling.ceilRaiseByValue,null,16.,16.,diffh*.4) )
|
|
{
|
|
ctics = int(diffh*.4/16.)+40;
|
|
cphase = 1;
|
|
}
|
|
}
|
|
else if ( cphase == 1 )
|
|
{
|
|
ctics--;
|
|
if ( (ctics <= 0) && level.CreateCeiling(csec,Ceiling.ceilRaiseByValue,null,1.,1.,diffh*.6) )
|
|
{
|
|
ctics = int(diffh*.6)+10;
|
|
cphase = 2;
|
|
}
|
|
}
|
|
else if ( cphase == 2 )
|
|
{
|
|
ctics--;
|
|
if ( ctics <= 0 ) cphase = 3;
|
|
}
|
|
else if ( (cphase >= 3) && csec.ceilingdata )
|
|
{
|
|
csec.ceilingdata.Destroy();
|
|
csec.StopSoundSequence(CHAN_VOICE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// cache data for manual lockdefs parsing nonsense
|
|
Class LIEntry
|
|
{
|
|
int locknumber;
|
|
bool hascolor;
|
|
Color mapcolor;
|
|
}
|
|
|
|
Class SWWMCachedLockInfo : Thinker
|
|
{
|
|
Array<LIEntry> ent;
|
|
|
|
static clearscope bool IsValidLock( int l )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMCachedLockInfo",STAT_STATIC);
|
|
SWWMCachedLockInfo cli = SWWMCachedLockInfo(ti.Next());
|
|
if ( !cli ) return false;
|
|
for ( int i=0; i<cli.ent.Size(); i++ )
|
|
{
|
|
if ( cli.ent[i].locknumber == l )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static clearscope Color GetLockColor( int l )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMCachedLockInfo",STAT_STATIC);
|
|
SWWMCachedLockInfo cli = SWWMCachedLockInfo(ti.Next());
|
|
if ( !cli ) return 0;
|
|
for ( int i=0; i<cli.ent.Size(); i++ )
|
|
{
|
|
if ( (cli.ent[i].locknumber == l) && cli.ent[i].hascolor )
|
|
return cli.ent[i].mapcolor;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static SWWMCachedLockInfo GetInstance()
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMCachedLockInfo",STAT_STATIC);
|
|
SWWMCachedLockInfo cli = SWWMCachedLockInfo(ti.Next());
|
|
if ( cli ) return cli;
|
|
cli = new("SWWMCachedLockInfo");
|
|
cli.ChangeStatNum(STAT_STATIC);
|
|
return cli;
|
|
}
|
|
}
|
|
|
|
Class SWWMCorpseCleaner : Thinker
|
|
{
|
|
transient ThinkerIterator ti;
|
|
Array<Actor> toclean;
|
|
int nstep, i;
|
|
|
|
private bool CmpDist( Actor activator, Vector3 a, Vector3 b )
|
|
{
|
|
double dista = level.Vec3Diff(activator.pos,a).length();
|
|
double distb = level.Vec3Diff(activator.pos,b).length();
|
|
return (dista > distb);
|
|
}
|
|
|
|
private int partition_cleanup( Actor activator, Array<Actor> a, int l, int h )
|
|
{
|
|
Actor pv = a[h];
|
|
int i = (l-1);
|
|
for ( int j=l; j<=(h-1); j++ )
|
|
{
|
|
if ( CmpDist(activator,pv.pos,a[j].pos) )
|
|
{
|
|
i++;
|
|
Actor tmp = a[j];
|
|
a[j] = a[i];
|
|
a[i] = tmp;
|
|
}
|
|
}
|
|
Actor tmp = a[h];
|
|
a[h] = a[i+1];
|
|
a[i+1] = tmp;
|
|
return i+1;
|
|
}
|
|
private void qsort_cleanup( Actor activator, Array<Actor> a, int l, int h )
|
|
{
|
|
if ( l >= h ) return;
|
|
int p = partition_cleanup(activator,a,l,h);
|
|
qsort_cleanup(activator,a,l,p-1);
|
|
qsort_cleanup(activator,a,p+1,h);
|
|
}
|
|
|
|
void Init( Actor activator )
|
|
{
|
|
toclean.Clear();
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( !a.bKILLED || (a.tics != -1) ) continue;
|
|
toclean.Push(a);
|
|
}
|
|
if ( toclean.Size() <= 0 )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
nstep = max(1,toclean.Size()/100);
|
|
i = 0;
|
|
// sort by distance to activator (if any)
|
|
if ( !activator ) return;
|
|
qsort_cleanup(activator,toclean,0,toclean.Size()-1);
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
for ( int j=0; j<nstep; j++ )
|
|
{
|
|
if ( i >= toclean.Size() )
|
|
{
|
|
Console.Printf("%d corpses cleaned.",toclean.Size());
|
|
Destroy();
|
|
return;
|
|
}
|
|
Actor a = toclean[i++];
|
|
if ( !a || !a.bKILLED || (a.tics != -1) ) continue;
|
|
let f = a.Spawn("SWWMItemFog",a.pos);
|
|
f.A_StartSound("bestsound",CHAN_ITEM);
|
|
a.Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
Class SWWMFlyTracker : Thinker
|
|
{
|
|
Actor tracked, instigator;
|
|
Vector3 startpos, curpos;
|
|
double maxdist;
|
|
int gracepd;
|
|
|
|
static void Track( Actor b, Actor whomst )
|
|
{
|
|
if ( !b || !whomst ) return;
|
|
let ti = ThinkerIterator.Create("SWWMFlyTracker",STAT_USER);
|
|
SWWMFlyTracker ffd;
|
|
while ( ffd = SWWMFlyTracker(ti.Next()) )
|
|
{
|
|
if ( ffd.tracked != b ) continue;
|
|
ffd.instigator = whomst;
|
|
return;
|
|
}
|
|
ffd = new("SWWMFlyTracker");
|
|
ffd.ChangeStatNum(STAT_USER);
|
|
ffd.tracked = b;
|
|
ffd.instigator = whomst;
|
|
ffd.curpos = ffd.startpos = b.pos;
|
|
ffd.maxdist = 0;
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
maxdist = max(maxdist,level.Vec3Diff(startpos,curpos).length());
|
|
if ( !tracked || tracked.bFLOAT || tracked.bNOGRAVITY || (tracked.waterlevel > 1) || (tracked.pos.z <= tracked.floorz) || !tracked.TestMobjZ(false) )
|
|
{
|
|
gracepd++;
|
|
if ( gracepd < 10 ) return;
|
|
if ( instigator ) SWWMUtility.AchievementProgress('swwm_progress_flight',int(maxdist),instigator.player);
|
|
Destroy();
|
|
return;
|
|
}
|
|
gracepd = 0;
|
|
curpos = tracked.pos;
|
|
}
|
|
|
|
}
|