From e5e6ce619c6aeac875dc01f59459f675c872b90b Mon Sep 17 00:00:00 2001 From: Marisa the Magician Date: Tue, 19 Dec 2023 11:46:29 +0100 Subject: [PATCH] Fix severe performance issues in large maps. So... Remember that one decision I made about avoiding BlockThingsIterator as much as possible? Turns out that was a stupid idea. There ARE situations where it's better to iterate sector thinglists, yes, especially for things that are NOT part of the blockmap, but in other cases, the excess allocations of new iterators are a reasonable price to pay for the lower perf impact in extreme cases, such as maps that have a gazillion sectors with gazillions of things in them (I'm looking at you, UDMF mappers). As a compromise, at least, in situations where the thinglists are needed, I did add a sort of micro-optimization by implementing code to check if a bounding box is inside a sector (would be nice if this was part of GZDoom itself, tho). --- language.version | 4 +- zscript/dlc1/swwm_mister_fx.zsc | 19 ++- zscript/handler/swwm_handler_debugrender.zsc | 22 +-- zscript/handler/swwm_handler_worldload.zsc | 78 ++++++++++ zscript/handler/swwm_handler_worldthings.zsc | 16 +-- zscript/handler/swwm_handler_worldtick.zsc | 72 +++++----- zscript/handler/swwm_statichandler.zsc | 4 + zscript/items/swwm_ammoextra.zsc | 2 +- zscript/items/swwm_lamp.zsc | 30 ++-- zscript/items/swwm_powerups.zsc | 8 +- zscript/items/swwm_powerups_vip.zsc | 31 ++-- zscript/player/swwm_player_tick.zsc | 32 ++--- zscript/utility/swwm_utility_blast.zsc | 58 +++++--- zscript/weapons/swwm_baseweapon_melee.zsc | 136 +++++++++--------- zscript/weapons/swwm_blazeit_fx.zsc | 19 ++- .../weapons/swwm_deathlydeathcannon_altfx.zsc | 31 ++-- .../weapons/swwm_deathlydeathcannon_fx.zsc | 26 ++-- zscript/weapons/swwm_deepdarkimpact.zsc | 47 +++--- zscript/weapons/swwm_sparkyboi_fx.zsc | 62 +++++--- zscript/weapons/swwm_splode_fx.zsc | 23 ++- 20 files changed, 442 insertions(+), 278 deletions(-) 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 ) {