// various stat tracking thinkers and others // Korax instakill handler Class KoraxYeeted : 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(); } } // ensures a polyobj stays out of bounds FOREVER Class SWWMBustedPolyobj : swwm_PolyobjectEffector { Actor whomstdve; override void PolyTick() { if ( Polyobject.GetPos() == (32000,32000) ) return; double dist = (Polyobject.GetPos()-(32000,32000)).length(); Level.ExecuteSpecial(Polyobj_Stop,whomstdve,Polyobject.StartLine,Line.Front,Polyobject.PolyobjectNum); if ( Polyobject.Mirror ) Level.ExecuteSpecial(Polyobj_Stop,whomstdve,Polyobject.Mirror.StartLine,Line.Front,Polyobject.Mirror.PolyobjectNum); Level.ExecuteSpecial(Polyobj_MoveTo,whomstdve,Polyobject.StartLine,Line.Front,Polyobject.PolyobjectNum,int(dist*8),32000,32000); if ( Polyobject.Mirror ) Level.ExecuteSpecial(Polyobj_Stop,whomstdve,Polyobject.Mirror.StartLine,Line.Front,Polyobject.Mirror.PolyobjectNum); } } // prevents floors/ceilings from ever moving again, as they're "broken crushers" Class SWWMCrusherBroken : Thinker { Sector fsec, csec; double diffh; int fphase, cphase; int ftics, ctics; SectorEffect fse, cse; // pointers to zero-speed movers static void Create( Sector f, Sector c, double diffh ) { 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) ) 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(); } static void Remove( Sector f, Sector c ) { 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) ) cb.Destroy(); // destroy entirely else if ( f && (cb.fsec == f) ) cb.fsec = null; // only clear the floor else if ( c && (cb.csec == c) ) cb.csec = null; // only clear the ceiling } } 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) && (!fse || (fsec.floordata != fse)) ) { if ( fsec.floordata ) fsec.floordata.Destroy(); level.CreateFloor(fsec,Floor.floorLowerByValue,null,0.,1.); fsec.StopSoundSequence(CHAN_WEAPON); fse = fsec.floordata; } } 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) && (!cse || (csec.ceilingdata != cse)) ) { if ( csec.ceilingdata ) csec.ceilingdata.Destroy(); level.CreateCeiling(csec,Ceiling.ceilRaiseByValue,null,0.,0.,1.); csec.StopSoundSequence(CHAN_VOICE); cse = csec.ceilingdata; } } } } Class SWWMCorpseCleaner : Thinker { transient ThinkerIterator ti; Array 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 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 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= 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 OnelinerHistory { String type; Array lines; } Class SWWMGlobals : SWWMStaticThinker { // set so these messages only happen once bool ccstartonce, cclilithonce; // for oneliners Array lastlines; // toggleable mapping stuff bool disablestore, disablerevive; static play SWWMGlobals Get() { let ti = ThinkerIterator.Create('SWWMGlobals',STAT_STATIC); SWWMGlobals g = SWWMGlobals(ti.Next()); if ( !g ) { g = new('SWWMGlobals'); g.ChangeStatNum(STAT_STATIC); } return g; } }