diff --git a/language.version b/language.version index 26ffb716e..34c3f2847 100644 --- a/language.version +++ b/language.version @@ -1,3 +1,3 @@ [default] -SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r1055 \cu(Mon 18 Dec 23:21:59 CET 2023)\c-"; -SWWM_SHORTVER="\cw1.3pre r1055 \cu(2023-12-18 23:21:59)\c-"; +SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r1056 \cu(Tue 19 Dec 11:46:29 CET 2023)\c-"; +SWWM_SHORTVER="\cw1.3pre r1056 \cu(2023-12-19 11:46:29)\c-"; diff --git a/zscript/dlc1/swwm_mister_fx.zsc b/zscript/dlc1/swwm_mister_fx.zsc index 1b9cbe1d2..072e00176 100644 --- a/zscript/dlc1/swwm_mister_fx.zsc +++ b/zscript/dlc1/swwm_mister_fx.zsc @@ -1039,19 +1039,16 @@ Class MisterGrenade : Actor if ( bNoProx ) return; // "safe delay" for main grenade if ( !bAMBUSH && (ReactionTime > default.ReactionTime-20) ) return; - bool breakout = false; - foreach ( s:level.Sectors ) + let bt = BlockThingsIterator.Create(self,bAMBUSH?80:120); + while ( bt.Next() ) { - for ( Actor t=s.thinglist; t; t=t.snext ) - { - if ( !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),bAMBUSH?80:120) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; - special1++; - tracer = t; - breakout = true; - break; - } - if ( breakout ) break; + let t = bt.Thing; + if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),bAMBUSH?80:120) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + special1++; + tracer = t; + break; } + bt.Destroy(); } // quicksort (seeking candidates) diff --git a/zscript/handler/swwm_handler_debugrender.zsc b/zscript/handler/swwm_handler_debugrender.zsc index e6f9de77b..a56b52050 100644 --- a/zscript/handler/swwm_handler_debugrender.zsc +++ b/zscript/handler/swwm_handler_debugrender.zsc @@ -102,15 +102,21 @@ extend Class SWWMHandler if ( !swwm_debugview ) return; // prepare projection data, we're going to need this SWWMUtility.PrepareProjData(projdata,e.ViewPos,e.ViewAngle,e.ViewPitch,e.ViewRoll,players[consoleplayer].fov); - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + foreach ( s:level.Sectors ) { - if ( (a == players[consoleplayer].Camera) && !(players[consoleplayer].cheats&CF_CHASECAM) ) continue; - if ( a.bINVISIBLE && !(a is 'DynamicLight') ) continue; - if ( (a is 'Inventory') && Inventory(a).Owner ) continue; - if ( (a is 'SWWMPickupFlash') && (a.CurState == a.FindState('Pickup')) ) continue; - if ( (a is 'SWWMShadow') || (a is 'SWWMItemOverlay') || (a is 'HeadpatTracker') || (a is 'SWWMTeleportLine') || (a is 'SWWMTeleportDest') ) continue; - if ( a.Distance3DSquared(e.Camera) > 1000000 ) continue; - DrawActor(e,a); + // don't check sectors that aren't within bounds, saves some time + if ( !BoxInSectorBounds(s,players[consoleplayer].Camera.pos.xy,1000,players[consoleplayer].Camera.CurSector.PortalGroup) ) + continue; + for ( Actor a=s.thinglist; a; a=a.snext ) + { + if ( (a == players[consoleplayer].Camera) && !(players[consoleplayer].cheats&CF_CHASECAM) ) continue; + if ( a.bINVISIBLE && !(a is 'DynamicLight') ) continue; + if ( (a is 'Inventory') && Inventory(a).Owner ) continue; + if ( (a is 'SWWMPickupFlash') && (a.CurState == a.FindState('Pickup')) ) continue; + if ( (a is 'SWWMShadow') || (a is 'SWWMItemOverlay') || (a is 'HeadpatTracker') || (a is 'SWWMTeleportLine') || (a is 'SWWMTeleportDest') ) continue; + if ( a.Distance3DSquared(e.Camera) > 1000000 ) continue; + DrawActor(e,a); + } } } } diff --git a/zscript/handler/swwm_handler_worldload.zsc b/zscript/handler/swwm_handler_worldload.zsc index be0debd4a..203bac29b 100644 --- a/zscript/handler/swwm_handler_worldload.zsc +++ b/zscript/handler/swwm_handler_worldload.zsc @@ -17,10 +17,42 @@ Class RampancyLogonDummy : Actor } } +// this is used to speed up iteration through sector thinglists within a specific area +Class SectorBounds +{ + Vector4 bounds; + int portalgroup; + + clearscope bool PointInSectorBounds( Vector2 p, int pg = -1 ) const + { + if ( (pg >= 0) && (level.GetPortalGroupCount() > 0) && (pg != portalgroup) ) + p += level.GetDisplacement(pg,portalgroup); + if ( p.x < bounds.x ) return false; + if ( p.y < bounds.y ) return false; + if ( p.x > bounds.z ) return false; + if ( p.y > bounds.w ) return false; + return true; + } + + clearscope bool BoxInSectorBounds( Vector2 p, double r, int pg = -1 ) const + { + if ( (pg >= 0) && (level.GetPortalGroupCount() > 0) && (pg != portalgroup) ) + p += level.GetDisplacement(pg,portalgroup); + if ( p.x+r < bounds.x ) return false; + if ( p.y+r < bounds.y ) return false; + if ( p.x-r > bounds.z ) return false; + if ( p.y-r > bounds.w ) return false; + return true; + } +} + extend Class SWWMHandler { bool maphaskeys; + // weird optimization + Array sbounds; + // level end stats override void WorldUnloaded( WorldEvent e ) { @@ -146,6 +178,51 @@ extend Class SWWMHandler SWWMUtility.MarkAchievement("hurry",players[consoleplayer]); } + clearscope bool PointInSectorBounds( Sector s, Vector2 p, int pg = -1 ) const + { + if ( sbounds.Size() <= 0 ) return false; + int i = s.Index(); + return sbounds[i].PointInSectorBounds(p,pg); + } + + clearscope bool BoxInSectorBounds( Sector s, Vector2 p, double r, int pg = -1 ) const + { + if ( sbounds.Size() <= 0 ) return false; + int i = s.Index(); + return sbounds[i].BoxInSectorBounds(p,r,pg); + } + + void PrecalculateSectorBounds() + { + sbounds.Resize(level.Sectors.Size()); + foreach ( s:level.Sectors ) + { + let sb = new("SectorBounds"); + sb.portalgroup = s.portalgroup; + sb.bounds = ( 32767, 32767, -32768, -32768 ); + foreach ( l:s.Lines ) + { + if ( l.v1.p.x < sb.bounds.x ) + sb.bounds.x = l.v1.p.x; + if ( l.v2.p.x < sb.bounds.x ) + sb.bounds.x = l.v2.p.x; + if ( l.v1.p.y < sb.bounds.y ) + sb.bounds.y = l.v1.p.y; + if ( l.v2.p.y < sb.bounds.y ) + sb.bounds.y = l.v2.p.y; + if ( l.v1.p.x > sb.bounds.z ) + sb.bounds.z = l.v1.p.x; + if ( l.v2.p.x > sb.bounds.z ) + sb.bounds.z = l.v2.p.x; + if ( l.v1.p.y > sb.bounds.w ) + sb.bounds.w = l.v1.p.y; + if ( l.v2.p.y > sb.bounds.w ) + sb.bounds.w = l.v2.p.y; + } + sbounds[s.Index()] = sb; + } + } + private void MapStartDialogues() { int whichboss = WhichVanillaBossMap(); @@ -264,6 +341,7 @@ extend Class SWWMHandler // (if we do it in OnRegister the existing thinker might not have been deserialized yet) gdat = SWWMGlobals.Get(); if ( e.IsReopen ) return; + PrecalculateSectorBounds(); MapStartDialogues(); if ( gamestate != GS_TITLELEVEL ) { diff --git a/zscript/handler/swwm_handler_worldthings.zsc b/zscript/handler/swwm_handler_worldthings.zsc index 5bcbdd6ac..9fb85b456 100644 --- a/zscript/handler/swwm_handler_worldthings.zsc +++ b/zscript/handler/swwm_handler_worldthings.zsc @@ -172,18 +172,12 @@ extend Class SWWMHandler // copies the floatbob of overlapping identical items, so it doesn't look weird private void CopyFloatBob( Actor a ) { - bool breakout = false; - foreach ( s:level.Sectors ) + for ( Actor t=a.CurSector.thinglist; t; t=t.snext ) { - for ( Actor t=s.thinglist; t; t=t.snext ) - { - if ( (t == a) || !(t is 'Inventory') || !(t.spawnpoint ~== a.spawnpoint) ) continue; - a.floatbobphase = t.floatbobphase; - a.angle = t.angle; // also copy angle - breakout = true; - break; - } - if ( breakout ) break; + if ( (t == a) || !(t is 'Inventory') || !(t.spawnpoint ~== a.spawnpoint) ) continue; + a.floatbobphase = t.floatbobphase; + a.angle = t.angle; // also copy angle + break; } } diff --git a/zscript/handler/swwm_handler_worldtick.zsc b/zscript/handler/swwm_handler_worldtick.zsc index dcec0adfc..37ec33e1f 100644 --- a/zscript/handler/swwm_handler_worldtick.zsc +++ b/zscript/handler/swwm_handler_worldtick.zsc @@ -281,42 +281,48 @@ extend Class SWWMHandler double viewdist = SWWMStatusBar.MAPVIEWDIST; // still about as expensive as using a BlockThingsIterator, but without the need to allocate one every tic int thisgroup = players[consoleplayer].Camera.CurSector.portalgroup; - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + foreach ( s:level.Sectors ) { - Vector2 rv; - if ( s.portalgroup != thisgroup ) + // don't check sectors that aren't within bounds, saves some time + if ( !BoxInSectorBounds(s,players[consoleplayer].Camera.pos.xy,viewdist,players[consoleplayer].Camera.CurSector.PortalGroup) ) + continue; + for ( Actor a=s.thinglist; a; a=a.snext ) { - Vector2 relpos = players[consoleplayer].Camera.pos.xy+level.GetDisplacement(thisgroup,s.portalgroup); - rv = a.pos.xy-relpos; + Vector2 rv; + if ( s.portalgroup != thisgroup ) + { + Vector2 relpos = players[consoleplayer].Camera.pos.xy+level.GetDisplacement(thisgroup,s.portalgroup); + rv = a.pos.xy-relpos; + } + else rv = a.pos.xy-players[consoleplayer].Camera.pos.xy; + double rad; + bool isproj = a.bMISSILE&&SWWMUtility.ValidProjectile(a); + if ( SWWMUtility.IsBeamProj(a) ) + { + isproj = true; + rad = SWWMUtility.IsYBeam(a)?(a.scale.y*cos(a.pitch-90)):(a.speed*cos(a.pitch)); + } + else rad = a.radius; + if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > viewdist ) + continue; + if ( a == players[consoleplayer].Camera ) + continue; + if ( a is 'GhostTarget' ) + continue; + if ( !a.player && !a.bSOLID && !a.bSHOOTABLE && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') && !isproj ) + continue; + if ( !level.allmap && !(deathmatch && (a is 'Inventory') && !a.bDROPPED) && !(a.IsFriend(players[consoleplayer].mo) && !(a.player && (a.player.mo != a))) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) + continue; + if ( a.bKILLED || (a.Health <= 0) || a.bUnmorphed ) + continue; + if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner || (a.GetClassName() == 'aas_token')) ) // autoautosave hotfix + continue; + if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) ) + continue; + if ( isproj && !level.allmap && !(a.target && a.target.IsFriend(players[consoleplayer].mo)) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) + continue; + SWWMSimpleTracker.Track(self,a); } - else rv = a.pos.xy-players[consoleplayer].Camera.pos.xy; - double rad; - bool isproj = a.bMISSILE&&SWWMUtility.ValidProjectile(a); - if ( SWWMUtility.IsBeamProj(a) ) - { - isproj = true; - rad = SWWMUtility.IsYBeam(a)?(a.scale.y*cos(a.pitch-90)):(a.speed*cos(a.pitch)); - } - else rad = a.radius; - if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > viewdist ) - continue; - if ( a == players[consoleplayer].Camera ) - continue; - if ( a is 'GhostTarget' ) - continue; - if ( !a.player && !a.bSOLID && !a.bSHOOTABLE && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') && !isproj ) - continue; - if ( !level.allmap && !(deathmatch && (a is 'Inventory') && !a.bDROPPED) && !(a.IsFriend(players[consoleplayer].mo) && !(a.player && (a.player.mo != a))) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) - continue; - if ( a.bKILLED || (a.Health <= 0) || a.bUnmorphed ) - continue; - if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner || (a.GetClassName() == 'aas_token')) ) // autoautosave hotfix - continue; - if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) ) - continue; - if ( isproj && !level.allmap && !(a.target && a.target.IsFriend(players[consoleplayer].mo)) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) - continue; - SWWMSimpleTracker.Track(self,a); } SWWMSimpleTracker trk = strackers; SWWMSimpleTracker prev = null, next; diff --git a/zscript/handler/swwm_statichandler.zsc b/zscript/handler/swwm_statichandler.zsc index 1c863600f..f1fe9a992 100644 --- a/zscript/handler/swwm_statichandler.zsc +++ b/zscript/handler/swwm_statichandler.zsc @@ -99,6 +99,10 @@ Class SWWMStaticHandler : StaticEventHandler SWWMHandler.ClearAllShaders(); EventHandler.SendInterfaceEvent(consoleplayer,"swwmflushhud"); EventHandler.SendInterfaceEvent(consoleplayer,"swwmaprcheck"); + // quick fix for old savegames + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + if ( hnd && (hnd.sbounds.Size() <= 0) ) + hnd.PrecalculateSectorBounds(); if ( !e.IsSaveGame ) return; // save version checker tainted = false; diff --git a/zscript/items/swwm_ammoextra.zsc b/zscript/items/swwm_ammoextra.zsc index f45f5e642..4cf174fd3 100644 --- a/zscript/items/swwm_ammoextra.zsc +++ b/zscript/items/swwm_ammoextra.zsc @@ -524,7 +524,7 @@ Class HammerspaceEmbiggener : Inventory if ( gameinfo.gametype&GAME_DoomChex ) A_SetSize(-1,26); int tamount = Amount; - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; ) + for ( Actor t=CurSector.thinglist; t; ) { let next = t.snext; if ( (t == self) || !(t is 'HammerspaceEmbiggener') || !(t.spawnpoint ~== spawnpoint) ) diff --git a/zscript/items/swwm_lamp.zsc b/zscript/items/swwm_lamp.zsc index d46d9d815..9546fee6a 100644 --- a/zscript/items/swwm_lamp.zsc +++ b/zscript/items/swwm_lamp.zsc @@ -5,6 +5,7 @@ Class LampMoth : Actor Actor lamp; Vector3 trail, ofs; int lifespan; + SWWMHandler hnd; Default { @@ -59,18 +60,25 @@ Class LampMoth : Actor { // look for nearby lamps double mindist = 62500.; - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + if ( !hnd ) SWWMHandler(EventHandler.Find("SWWMHandler")); + foreach ( s:level.Sectors ) { - if ( !(a is 'CompanionLamp') ) continue; - double dist = Distance3DSquared(a); - if ( (a.frame == 0) || (dist > mindist) && !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; - mindist = dist; - lamp = a; - master = a.target; - if ( CompanionLamp(lamp).moff.Find(self) == CompanionLamp(lamp).moff.Size() ) - CompanionLamp(lamp).moff.Push(self); - if ( master && master.player ) SetFriendPlayer(master.player); - else bFRIENDLY = false; + // don't check sectors that aren't within bounds, saves some time + if ( !hnd.BoxInSectorBounds(s,pos.xy,250,CurSector.PortalGroup) ) + continue; + for ( Actor a=s.thinglist; a; a=a.snext ) + { + if ( !(a is 'CompanionLamp') ) continue; + double dist = Distance3DSquared(a); + if ( (a.frame == 0) || (dist > mindist) && !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + mindist = dist; + lamp = a; + master = a.target; + if ( CompanionLamp(lamp).moff.Find(self) == CompanionLamp(lamp).moff.Size() ) + CompanionLamp(lamp).moff.Push(self); + if ( master && master.player ) SetFriendPlayer(master.player); + else bFRIENDLY = false; + } } } if ( !lamp || (lamp.frame == 0) || (Distance3DSquared(lamp) > 62500) || !CheckSight(lamp,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false; diff --git a/zscript/items/swwm_powerups.zsc b/zscript/items/swwm_powerups.zsc index 36fb945ac..a35ae6846 100644 --- a/zscript/items/swwm_powerups.zsc +++ b/zscript/items/swwm_powerups.zsc @@ -310,9 +310,11 @@ Class GhostTarget : Actor } if ( isFrozen() ) return; if ( diedie ) A_FadeOut(.05); - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + let bt = BlockThingsIterator.Create(self,256); + while ( bt.Next() ) { - if ( !t.bIsMonster || t.player || !t.IsHostile(master) || (t.target != self) ) continue; + let t = bt.Thing; + if ( !t || !t.bIsMonster || t.player || !t.IsHostile(master) || (t.target != self) ) continue; if ( SWWMUtility.BoxIntersect(self,t,pad:16) || t.CheckMeleeRange() ) { // they found out, there's no one here @@ -320,9 +322,11 @@ Class GhostTarget : Actor break; } } + bt.Destroy(); // player made noise or is visible again if ( !master || (LastHeard == master) || !master.FindInventory("GhostPower") ) { + // note: this is faster than using a thinkeriterator foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) { if ( !a.bISMONSTER || a.player || !a.IsHostile(master) || (a.Health <= 0) ) continue; diff --git a/zscript/items/swwm_powerups_vip.zsc b/zscript/items/swwm_powerups_vip.zsc index b83ab388f..cf47d4738 100644 --- a/zscript/items/swwm_powerups_vip.zsc +++ b/zscript/items/swwm_powerups_vip.zsc @@ -510,18 +510,27 @@ Class Mykradvo : Inventory { targets.Clear(); // search all actively hostile enemies within 50m - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + BlockThingsIterator bt; + for ( int i=0; i= targets.Size() ) + targets.Push(a); + } + bt.Destroy(); } // sorted, so the closest take priority qsort_targets(targets,0,targets.Size()-1,t); diff --git a/zscript/player/swwm_player_tick.zsc b/zscript/player/swwm_player_tick.zsc index 73e288ec5..1b4d710b9 100644 --- a/zscript/player/swwm_player_tick.zsc +++ b/zscript/player/swwm_player_tick.zsc @@ -5,8 +5,11 @@ extend Class Demolitionist { if ( player.cmd.buttons&BT_USER3 ) { - foreach ( s:level.Sectors ) for ( Actor i=s.thinglist; i; i=i.snext ) + let bt = BlockThingsIterator.Create(self,800); + while ( bt.Next() ) { + let i = bt.Thing; + if ( !i ) continue; if ( !(i is 'Inventory') && !(i is 'Chancebox') && !(i is 'SWWMRespawnTimer') ) continue; if ( (i is 'Inventory') && (i.bINVISIBLE || !i.bSPECIAL || Inventory(i).Owner) ) continue; if ( (i is 'Chancebox') && (i.CurState != i.SpawnState) ) continue; @@ -14,6 +17,7 @@ extend Class Demolitionist if ( !level.allmap && !i.CheckSight(self,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; SWWMItemSense.Spawn(self,i); } + bt.Destroy(); } SWWMItemsense itm = itemsense; SWWMItemsense prev = null, next; @@ -50,8 +54,11 @@ extend Class Demolitionist } if ( (magitem_cnt < 8) && !swwm_usetopickup ) { - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + let bt = BlockThingsIterator.Create(self,500); + while ( bt.Next() ) { + let t = bt.Thing; + if ( !t ) continue; if ( !(t is 'Inventory') || !t.bSPECIAL || !t.bDROPPED || t.bINVISIBLE || Inventory(t).Owner || !SWWMUtility.SphereIntersect(t,pos,500) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; let i = Inventory(t); @@ -82,6 +89,7 @@ extend Class Demolitionist magitem = nmi; magitem_cnt++; } + bt.Destroy(); } SWWMMagItem itm = magitem; SWWMMagItem prev = null, next; @@ -548,12 +556,12 @@ extend Class Demolitionist let raging = RagekitPower(FindInventory("RagekitPower")); double maxmass = max(mass*spd/40.,200); if ( raging ) maxmass *= 2; - Array hitlist; - hitlist.Clear(); - // gather first - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + let bt = BlockThingsIterator.Create(self,spd+radius+1024); + while ( bt.Next() ) { - if ( (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS || a.bCORPSE || !CanCollideWith(a,false) || !a.CanCollideWith(self,true) ) continue; + if ( spd <= 0 ) break; // if something stopped us, no more iteration + let a = bt.Thing; + if ( !a || (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS || a.bCORPSE || !CanCollideWith(a,false) || !a.CanCollideWith(self,true) ) continue; if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*(spd+radius),8) ) continue; if ( (a.pos.z <= a.floorz) && (a.height <= MaxStepHeight) ) continue; Vector3 diff = level.Vec3Diff(pos,a.pos); @@ -574,15 +582,6 @@ extend Class Demolitionist if ( dir dot bnorm > -.6 ) continue; } if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; - hitlist.Push(a); - } - foreach ( a:hitlist ) - { - if ( !a ) continue; - if ( spd <= 0 ) break; - // we have to recalculate these - Vector3 diff = level.Vec3Diff(pos,a.pos); - Vector3 dirto = diff.unit(); // large monsters will stop the player (unless hit from above if we're going at ground pound speed) A_QuakeEx(4.,4.,4.,10,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D); A_AlertMonsters(swwm_uncapalert?0:800); @@ -647,6 +646,7 @@ extend Class Demolitionist raging.DoHitFX(); } } + bt.Destroy(); // check for ceiling collision if ( (spd > 0) && !bumped && ((pos.z+Height+dir.z*spd) >= ceilingz) ) { diff --git a/zscript/utility/swwm_utility_blast.zsc b/zscript/utility/swwm_utility_blast.zsc index 58e1dfd52..37bf24feb 100644 --- a/zscript/utility/swwm_utility_blast.zsc +++ b/zscript/utility/swwm_utility_blast.zsc @@ -68,30 +68,42 @@ extend Class SWWMUtility Array hitlist; hitlist.Clear(); // gather first - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + BlockThingsIterator bt; + int thisgroup = Source.CurSector.PortalGroup; + // we need to check all possible portal groups due to quirks of blockthingsiterator and sector portals + for ( int i=0; i= hitlist.Size() ) + hitlist.Push(a); + } + bt.Destroy(); } foreach ( a:hitlist ) { diff --git a/zscript/weapons/swwm_baseweapon_melee.zsc b/zscript/weapons/swwm_baseweapon_melee.zsc index 6ef9ddcf1..71e2357f2 100644 --- a/zscript/weapons/swwm_baseweapon_melee.zsc +++ b/zscript/weapons/swwm_baseweapon_melee.zsc @@ -101,6 +101,7 @@ Class ParryField : SWWMNonInteractiveActor { bool critsnd; Array justparried; + SWWMHandler hnd; override void Tick() { @@ -113,78 +114,85 @@ Class ParryField : SWWMNonInteractiveActor let raging = RagekitPower(master.FindInventory("RagekitPower")); let st = Demolitionist(master).mystats; // check for projectiles to deflect - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + if ( !hnd ) hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + foreach ( s:level.Sectors ) { - if ( (justparried.Find(a) < justparried.Size()) || !(SWWMUtility.ValidProjectile(a) || a.bSKULLFLY) || a.bTHRUACTORS || (level.Vec3Diff(a.pos,pos).length() > 80) ) continue; - if ( a is 'Whirlwind' ) SWWMUtility.MarkAchievement("tornado",master.player); - justparried.Push(a); - Vector3 vdir = a.vel; - Vector3 dir = level.Vec3Diff(master.Vec2OffsetZ(0,0,pos.z),a.pos).unit(); - Vector3 hdir = dir; - Actor oldtarget = a.target; - if ( (a.target != master) && (a.bMISSILE || (a is 'HolySpirit')) ) // special wraithverge handling + // don't check sectors that aren't within bounds, saves some time + if ( !hnd.BoxInSectorBounds(s,pos.xy,80,CurSector.PortalGroup) ) + continue; + for ( Actor a=s.thinglist; a; a=a.snext ) { - // deflect directly to target - if ( a.target ) + if ( (justparried.Find(a) < justparried.Size()) || !(SWWMUtility.ValidProjectile(a) || a.bSKULLFLY) || a.bTHRUACTORS || (level.Vec3Diff(a.pos,pos).length() > 80) ) continue; + if ( a is 'Whirlwind' ) SWWMUtility.MarkAchievement("tornado",master.player); + justparried.Push(a); + Vector3 vdir = a.vel; + Vector3 dir = level.Vec3Diff(master.Vec2OffsetZ(0,0,pos.z),a.pos).unit(); + Vector3 hdir = dir; + Actor oldtarget = a.target; + if ( (a.target != master) && (a.bMISSILE || (a is 'HolySpirit')) ) // special wraithverge handling { - hdir = level.Vec3Diff(a.pos,a.target.Vec3Offset(0,0,a.target.height/2)).unit(); - double theta = max(FRandom[Parry](0.,1.)**2.,.1); - dir = SWWMUtility.LerpVector3(dir,hdir,theta); + // deflect directly to target + if ( a.target ) + { + hdir = level.Vec3Diff(a.pos,a.target.Vec3Offset(0,0,a.target.height/2)).unit(); + double theta = max(FRandom[Parry](0.,1.)**2.,.1); + dir = SWWMUtility.LerpVector3(dir,hdir,theta); + } + // push away + if ( a.bSEEKERMISSILE ) a.tracer = a.target; + a.target = master; + } + if ( a.bSKULLFLY ) a.bBLASTED = true; // blast lost souls + let buff = a.FindInventory("ParriedBuff"); + if ( !buff ) + { + buff = Inventory(Spawn("ParriedBuff")); + buff.AttachToOwner(a); + buff.tracer = oldtarget; + } + double mvel = a.vel.length(); + double nspeed = min(100,mvel*FRandom[Parry](1.2,1.4)+20); + a.angle = atan2(dir.y,dir.x); + a.pitch = asin(-dir.z); + if ( raging ) + { + buff.special1 |= 2; + nspeed = min(100,nspeed*2.); + raging.DoHitFX(); } - // push away - if ( a.bSEEKERMISSILE ) a.tracer = a.target; - a.target = master; - } - if ( a.bSKULLFLY ) a.bBLASTED = true; // blast lost souls - let buff = a.FindInventory("ParriedBuff"); - if ( !buff ) - { - buff = Inventory(Spawn("ParriedBuff")); - buff.AttachToOwner(a); - buff.tracer = oldtarget; - } - double mvel = a.vel.length(); - double nspeed = min(100,mvel*FRandom[Parry](1.2,1.4)+20); - a.angle = atan2(dir.y,dir.x); - a.pitch = asin(-dir.z); - if ( raging ) - { - buff.special1 |= 2; - nspeed = min(100,nspeed*2.); - raging.DoHitFX(); - } - a.vel = dir*nspeed; - if ( a.bMISSILE ) a.speed = nspeed; - let i = Spawn(raging?"BigPunchImpact":"PunchImpact",a.pos); - i.target = master; - i.angle = atan2(dir.y,dir.x); - i.pitch = asin(-dir.z); - i.bAMBUSH = true; - A_QuakeEx(3.,3.,3.,10,0,64,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.2); - A_StartSound("demolitionist/parry",CHAN_WEAPON); - if ( special1 >= special2 ) // perfect parry - { - // increased homing - dir = dir*.4+hdir*.6; - nspeed = min(100,nspeed*1.5); a.vel = dir*nspeed; - for ( int i=1; i<6; i++ ) + if ( a.bMISSILE ) a.speed = nspeed; + let i = Spawn(raging?"BigPunchImpact":"PunchImpact",a.pos); + i.target = master; + i.angle = atan2(dir.y,dir.x); + i.pitch = asin(-dir.z); + i.bAMBUSH = true; + A_QuakeEx(3.,3.,3.,10,0,64,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.2); + A_StartSound("demolitionist/parry",CHAN_WEAPON); + if ( special1 >= special2 ) // perfect parry { - let r = Spawn("ParryRing",a.pos); - r.specialf1 = i*.04; + // increased homing + dir = dir*.4+hdir*.6; + nspeed = min(100,nspeed*1.5); + a.vel = dir*nspeed; + for ( int i=1; i<6; i++ ) + { + let r = Spawn("ParryRing",a.pos); + r.specialf1 = i*.04; + } + buff.special1 |= 1; + if ( !critsnd ) + { + A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5); + if ( st ) st.pparries++; + } + critsnd = true; + if ( (a is 'LostSoul') && (master.player.ReadyWeapon is 'SilverBullet') ) + SWWMUtility.MarkAchievement("baseball",master.player); } - buff.special1 |= 1; - if ( !critsnd ) - { - A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5); - if ( st ) st.pparries++; - } - critsnd = true; - if ( (a is 'LostSoul') && (master.player.ReadyWeapon is 'SilverBullet') ) - SWWMUtility.MarkAchievement("baseball",master.player); + if ( st ) st.parries++; + SWWMUtility.AchievementProgressInc("parry",1,master.player); } - if ( st ) st.parries++; - SWWMUtility.AchievementProgressInc("parry",1,master.player); } if ( --special1 <= 0 ) Destroy(); } diff --git a/zscript/weapons/swwm_blazeit_fx.zsc b/zscript/weapons/swwm_blazeit_fx.zsc index 396ad167c..ee9490fc6 100644 --- a/zscript/weapons/swwm_blazeit_fx.zsc +++ b/zscript/weapons/swwm_blazeit_fx.zsc @@ -266,19 +266,16 @@ Class HellblazerMissile : Actor return; } // proximity check - bool checked = false; - foreach ( s:level.Sectors ) + let bt = BlockThingsIterator.Create(self,256); + while ( bt.Next() ) { - for ( Actor t=s.thinglist; t; t=t.snext ) - { - if ( !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),bNOGRAVITY?50:90) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; - deto++; - tracer = t; - checked = true; - break; - } - if ( checked ) break; + let t = bt.Thing; + if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),bNOGRAVITY?50:90) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + deto++; + tracer = t; + break; } + bt.Destroy(); } void A_BlazerMissileExplode() diff --git a/zscript/weapons/swwm_deathlydeathcannon_altfx.zsc b/zscript/weapons/swwm_deathlydeathcannon_altfx.zsc index a9c7327aa..9752e5119 100644 --- a/zscript/weapons/swwm_deathlydeathcannon_altfx.zsc +++ b/zscript/weapons/swwm_deathlydeathcannon_altfx.zsc @@ -952,6 +952,8 @@ Class YnykronSingularity : SWWMNonInteractiveActor transient SimpleMoveTracer mt; + SWWMHandler hnd; + override void PostBeginPlay() { let g = Spawn("YnykronHalo",pos); @@ -1083,19 +1085,26 @@ Class YnykronSingularity : SWWMNonInteractiveActor Array candidates; candidates.Clear(); // gather first - foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext ) + if ( !hnd ) hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + foreach ( s:level.Sectors ) { - if ( !a.bSHOOTABLE && !(a is 'Inventory') && !SWWMUtility.ValidProjectile(a) ) + // don't check sectors that aren't within bounds, saves some time + if ( !hnd.BoxInSectorBounds(s,pos.xy,20000.*scale.x,CurSector.PortalGroup) ) continue; - if ( a.bDORMANT || (a.Health <= 0) || (a == self) || !SWWMUtility.SphereIntersect(a,pos,20000.*scale.x) || !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) - continue; - if ( (a is 'Inventory') && !a.bDROPPED ) // must be a dropped pickup - continue; - if ( a.player && (a.player.cheats&CF_NOCLIP2) ) // for safety - continue; - if ( (a is 'YnykronSingularity') && a.bNOBLOCKMAP ) // dead singularity, ignore - continue; - candidates.Push(a); + for ( Actor a=s.thinglist; a; a=a.snext ) + { + if ( !a.bSHOOTABLE && !(a is 'Inventory') && !SWWMUtility.ValidProjectile(a) ) + continue; + if ( a.bDORMANT || (a.Health <= 0) || (a == self) || !SWWMUtility.SphereIntersect(a,pos,20000.*scale.x) || !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) + continue; + if ( (a is 'Inventory') && !a.bDROPPED ) // must be a dropped pickup + continue; + if ( a.player && (a.player.cheats&CF_NOCLIP2) ) // for safety + continue; + if ( (a is 'YnykronSingularity') && a.bNOBLOCKMAP ) // dead singularity, ignore + continue; + candidates.Push(a); + } } foreach ( a:candidates ) { diff --git a/zscript/weapons/swwm_deathlydeathcannon_fx.zsc b/zscript/weapons/swwm_deathlydeathcannon_fx.zsc index a907313b9..6e4464609 100644 --- a/zscript/weapons/swwm_deathlydeathcannon_fx.zsc +++ b/zscript/weapons/swwm_deathlydeathcannon_fx.zsc @@ -467,15 +467,25 @@ Class YnykronImpact : SWWMNonInteractiveActor } Array candidates; candidates.Clear(); - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + BlockThingsIterator bt; + // gather first, making sure to traverse through all portal groups just in case + for ( int i=0; i rad/2) && (t == target) ) continue; - candidates.Push(t); + Vector2 disp = level.GetDisplacement(CurSector.PortalGroup,i); + bt = BlockThingsIterator.CreateFromPos(pos.x+disp.x,pos.y+disp.y,pos.z,rad,rad,false); + while ( bt.Next() ) + { + let t = bt.Thing; + if ( !t || !t.bSHOOTABLE || !SWWMUtility.SphereIntersect(t,pos,rad) || (!SWWMUtility.SphereIntersect(t,pos,100) && !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY)) ) continue; + if ( YnykronShot(master) && (YnykronShot(master).hitlist.Find(t) < YnykronShot(master).hitlist.Size()) ) + continue; + Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.Height/2)); + double dist = dirto.length(); + if ( (dist > rad/2) && (t == target) ) continue; + if ( candidates.Find(t) >= candidates.Size() ) + candidates.Push(t); + } + bt.Destroy(); } qsort_candidates(candidates,0,candidates.Size()-1); candidates.Resize(2); diff --git a/zscript/weapons/swwm_deepdarkimpact.zsc b/zscript/weapons/swwm_deepdarkimpact.zsc index f1726667b..e69c303ec 100644 --- a/zscript/weapons/swwm_deepdarkimpact.zsc +++ b/zscript/weapons/swwm_deepdarkimpact.zsc @@ -212,29 +212,36 @@ Class DeepImpact : SWWMWeapon l.a.DamageMobj(p,self,int(dmg/250.),'Push',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF); } let st = Demolitionist(self).mystats; - foreach ( s:level.Sectors ) for ( Actor m=s.thinglist; m; m=m.snext ) + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + foreach ( s:level.Sectors ) { - if ( !SWWMUtility.ValidProjectile(m) ) continue; - Vector3 rdir = level.Vec3Diff(origin,m.pos); - double rdist = rdir.length(); - if ( rdist <= 0. ) continue; - rdir /= rdist; - if ( LineTrace(atan2(rdir.y,rdir.x),rdist,asin(-rdir.z),TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y) || (rdist > 250) || (rdir dot x < .75) ) continue; - m.speed = m.vel.length(); - m.vel = m.speed*1.5*(-m.vel.unit()*.3+rdir+x*.2).unit(); - Vector3 ndir = m.vel.unit(); - m.angle = atan2(ndir.y,ndir.x); - m.pitch = asin(-ndir.z); - if ( m.bSEEKERMISSILE && (m.target != self) ) m.tracer = m.target; - m.target = self; - if ( !m.FindInventory("ParriedBuff") ) + // don't check sectors that aren't within bounds, saves some time + if ( !hnd.BoxInSectorBounds(s,origin.xy,250,level.PointInSector(origin.xy).PortalGroup) ) + continue; + for ( Actor m=s.thinglist; m; m=m.snext ) { - let pb = Inventory(Spawn("ParriedBuff")); - pb.AttachToOwner(m); - pb.bAMBUSH = true; + if ( !SWWMUtility.ValidProjectile(m) ) continue; + Vector3 rdir = level.Vec3Diff(origin,m.pos); + double rdist = rdir.length(); + if ( rdist <= 0. ) continue; + rdir /= rdist; + if ( LineTrace(atan2(rdir.y,rdir.x),rdist,asin(-rdir.z),TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y) || (rdist > 250) || (rdir dot x < .75) ) continue; + m.speed = m.vel.length(); + m.vel = m.speed*1.5*(-m.vel.unit()*.3+rdir+x*.2).unit(); + Vector3 ndir = m.vel.unit(); + m.angle = atan2(ndir.y,ndir.x); + m.pitch = asin(-ndir.z); + if ( m.bSEEKERMISSILE && (m.target != self) ) m.tracer = m.target; + m.target = self; + if ( !m.FindInventory("ParriedBuff") ) + { + let pb = Inventory(Spawn("ParriedBuff")); + pb.AttachToOwner(m); + pb.bAMBUSH = true; + } + if ( st ) st.parries++; + SWWMUtility.AchievementProgressInc("parry",1,player); } - if ( st ) st.parries++; - SWWMUtility.AchievementProgressInc("parry",1,player); } int numpt = Random[Impact](7,12); for ( int i=0; i 64000000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || ((dist=Distance3DSquared(t)) > 64000000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.height/2)).unit(); if ( dist > closest ) continue; closest = dist; tracer = t; } + bt.Destroy(); } if ( tracer && (tracer.Health > 0) && CheckSight(tracer,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) { @@ -222,11 +225,20 @@ Class BigBiospark : Actor // deal (proper) radius damage Array hitlist; hitlist.Clear(); - // gather first - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + // gather first, making sure to traverse through all portal groups just in case + BlockThingsIterator bt; + for ( int i=0; i= hitlist.Size() ) + hitlist.Push(t); + } + bt.Destroy(); } foreach ( t:hitlist ) { @@ -466,16 +478,19 @@ Class BiosparkBall : Actor if ( !(special2%5) ) { double closest = double.infinity; - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + let bt = BlockThingsIterator.Create(self,8000); + while ( bt.Next() ) { + let t = bt.Thing; double dist; - if ( !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || ((dist=Distance3DSquared(t)) > 250000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || ((dist=Distance3DSquared(t)) > 250000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.height/2)).unit(); if ( dir dot dirto < .5 ) continue; // don't seek stuff that's behind us if ( dist > closest ) continue; closest = dist; tracer = t; } + bt.Destroy(); } if ( tracer && (tracer.Health > 0) && CheckSight(tracer,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) { @@ -496,18 +511,15 @@ Class BiosparkBall : Actor return; } // proximity check - bool checked = false; - foreach ( s:level.Sectors ) + let bt = BlockThingsIterator.Create(self,256); + while ( bt.Next() ) { - for ( Actor t=s.thinglist; t; t=t.snext ) - { - if ( !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain')) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),16) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; - deto++; - checked = true; - break; - } - if ( checked ) break; + let t = bt.Thing; + if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain')) || (t.Health <= 0) || (target && t.IsFriend(target)) || !SWWMUtility.SphereIntersect(t,level.Vec3Offset(pos,vel),16) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + deto++; + break; } + bt.Destroy(); } override void OnDestroy() { @@ -1058,16 +1070,19 @@ Class BiosparkBeam : SWWMNonInteractiveActor } nextpos = t.Results.HitPos; double closest = double.infinity; - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + let bt = BlockThingsIterator.Create(self,500); + while ( bt.Next() ) { + let t = bt.Thing; double dist; - if ( (!(t is 'BiosparkHitbox') && (!t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)))) || ((dist=Distance3DSquared(t)) > 250000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + if ( !t || (!(t is 'BiosparkHitbox') && (!t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)))) || ((dist=Distance3DSquared(t)) > 250000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; Vector3 dirto = level.Vec3Diff(nextpos,t.Vec3Offset(0,0,t.height/2)); if ( dir dot dirto < .2 ) continue; if ( dist > closest ) continue; closest = dist; tracer = t; } + bt.Destroy(); if ( tracer ) { Vector3 dirto = level.Vec3Diff(nextpos,tracer.Vec3Offset(0,0,tracer.height/2)); @@ -1363,16 +1378,19 @@ Class BiosparkArc : SWWMNonInteractiveActor else { double closest = double.infinity; - foreach ( s:level.Sectors ) for ( Actor t=s.thinglist; t; t=t.snext ) + let bt = BlockThingsIterator.Create(self,1500); + while ( bt.Next() ) { + let t = bt.Thing; double dist; - if ( !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || ((dist=Distance3DSquared(t)) > 2250000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; + if ( !t || !t.bSHOOTABLE || (!t.bISMONSTER && !(t is 'BossBrain') && !t.player) || (t.Health <= 0) || (target && t.IsFriend(target)) || ((dist=Distance3DSquared(t)) > 2250000) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; Vector3 dirto = level.Vec3Diff(nextpos,t.Vec3Offset(0,0,t.height/2)); if ( dir dot dirto < .2 ) continue; if ( dist > closest ) continue; closest = dist; tracer = t; } + bt.Destroy(); if ( tracer ) { Vector3 dirto = level.Vec3Diff(nextpos,tracer.Vec3Offset(0,0,tracer.height/2)); diff --git a/zscript/weapons/swwm_splode_fx.zsc b/zscript/weapons/swwm_splode_fx.zsc index 5a7dd5f52..7676aad66 100644 --- a/zscript/weapons/swwm_splode_fx.zsc +++ b/zscript/weapons/swwm_splode_fx.zsc @@ -245,21 +245,18 @@ Class ExplodiumMagHitbox : Actor return; } SetOrigin(target.Vec3Offset(0,0,-height*.5),false); - bool breakout = false; - foreach ( s:level.Sectors ) + let bt = BlockThingsIterator.Create(self,256); + while ( bt.Next() ) { - for ( Actor t=s.thinglist; t; t=t.snext ) - { - if ( (t == self) || !t.bSHOOTABLE || (t == target.target) || t.IsFriend(target.target) || !SWWMUtility.BoxIntersect(self,t) ) - continue; - target.bKILLED = true; - target.SetStateLabel("Detonate"); - Destroy(); - breakout = true; - break; - } - if ( breakout ) break; + let t = bt.Thing; + if ( !t || (t == self) || !t.bSHOOTABLE || (t == target.target) || t.IsFriend(target.target) || !SWWMUtility.BoxIntersect(self,t) ) + continue; + target.bKILLED = true; + target.SetStateLabel("Detonate"); + Destroy(); + break; } + bt.Destroy(); } override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle ) {