// Wallbuster effects Class BustedQuake : SWWMNonInteractiveActor { override void PostBeginPlay() { if ( (special1 < 3) || (special1 > 6) ) { A_StartSound("wallbuster/smallbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,special1*.32),1./max(1.,special1*.35),1.-special1*.05); A_StartSound("wallbuster/smallbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,special1*.32),1./max(1.,special1*.35),1.-special1*.05); } if ( special1 >= 3 ) { A_StartSound("wallbuster/bigbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,special1*.35),1./max(1.,special1*.35),1.-special1*.01); A_StartSound("wallbuster/bigbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,special1*.35),1./max(1.,special1*.35),1.-special1*.01); } A_QuakeEx(special1,special1,special1,20+special1*5,0,300+special1*90,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:special1*.1); A_AlertMonsters(swwm_uncapalert?0:2500); } States { Spawn: TNT1 A 700; Stop; } } Class BustPoint { Vector2 pos; } // Bustin' makes me feel good Class BusterWall : Thinker { Sector hitsector; swwm_PolyobjectHandle hitpoly; int accdamage; Array acchits; int hitplane; bool busted; Vector3 bustdir; int busttics, delay, bustmax; double cutheight; // cached Vector3 boundsmin, boundsmax, step; Array polygrid; override void Tick() { if ( busted ) { busttics++; if ( busttics > bustmax ) { Destroy(); return; } SpawnDebris(); return; } // fade out damage if ( delay > 0 ) { delay--; return; } accdamage = int(accdamage*.9-5); if ( accdamage <= 0 ) { Destroy(); return; } } private void SpawnDebris( bool initial = false ) { if ( hitpoly ) { SpawnDebrisPoly(initial); return; } double x, y, z; for ( z=boundsmin.z; z (bustmax/2)) ) continue; int numpt = Random[Wallbuster](-4,1); for ( int i=0; i (bustmax/2)) ) continue; int numpt = Random[Wallbuster](-4,1); for ( int i=0; i= ceil ) faketracer.Results.Tier = TIER_Upper; else if ( (a.pos.z+a.Height) <= flor ) faketracer.Results.Tier = TIER_Lower; } } else if ( a.BlockingMobj ) { Vector3 diff = level.Vec3Diff(a.BlockingMobj.Vec3Offset(0,0,a.BlockingMobj.Height/2),a.pos); HitNormal = diff.unit(); faketracer.Results.HitType = TRACE_HitActor; } return Bust(faketracer.Results,accdamage,a.target,x,a.pos.z+a.Height/2.); } static bool BustLinetrace( FLineTraceData d, int accdamage, Actor instigator, Vector3 x, double hitz ) { LineTracer faketracer = new("LineTracer"); faketracer.Results.HitType = d.HitType; faketracer.Results.HitSector = d.HitSector; faketracer.Results.HitLine = d.HitLine; faketracer.Results.ffloor = d.Hit3DFloor; faketracer.Results.Side = d.LineSide; faketracer.Results.Tier = (d.LinePart==Side.Top)?TIER_UPPER:(d.LinePart==Side.Bottom)?TIER_LOWER:TIER_Middle; return Bust(faketracer.Results,accdamage,instigator,x,hitz); } static bool BustPolyobj( swwm_PolyobjectHandle p, int accdamage, Actor instigator, Vector3 x ) { let ti = ThinkerIterator.Create("BusterWall",STAT_USER); BusterWall iter, bust = null; while ( iter = BusterWall(ti.Next()) ) { if ( iter.hitpoly != p ) continue; bust = iter; break; } bool mnew = false; if ( !bust ) { bust = new("BusterWall"); bust.ChangeStatNum(STAT_USER); bust.hitpoly = p; bust.accdamage = 0; bust.bustdir = x; mnew = true; } // multiply damage if ( instigator ) accdamage = instigator.GetModifiedDamage('Wallbust',accdamage,false,instigator,instigator,0); bust.delay = max(bust.delay,5+min(20,accdamage>>4)); bust.accdamage += accdamage; bust.acchits.Push(accdamage); bust.bustdir = (bust.bustdir+x)*.5; // skip if already busted if ( bust.busted ) return true; // not enough total damage if ( bust.accdamage < 100 ) return false; // estimate polyobject volume Vector3 a = (32767,32767,32767), b = (-32768,-32768,-32768); for ( int i=0; i b.x ) b.x = l.v1.p.x; if ( l.v2.p.x > b.x ) b.x = l.v2.p.x; if ( l.v1.p.y > b.y ) b.y = l.v1.p.y; if ( l.v2.p.y > b.y ) b.y = l.v2.p.y; Sector s = level.PointInSector(l.v1.p); double fz = s.floorplane.ZAtPoint(l.v1.p); double cz = s.ceilingplane.ZAtPoint(l.v1.p); if ( fz < a.z ) a.z = fz; if ( cz > b.z ) b.z = cz; s = level.PointInSector(l.v2.p); fz = s.floorplane.ZAtPoint(l.v2.p); cz = s.ceilingplane.ZAtPoint(l.v2.p); if ( fz < a.z ) a.z = fz; if ( cz > b.z ) b.z = cz; } double girthitude = (b.x-a.x)*(b.y-a.y)*(b.z-a.z); // do a grid check to approximate "real" volume double ystep = (b.y-a.y)/64.; double xstep = (b.x-a.x)/64.; int inspot = 0, allspot = 0; for ( double y=a.y; y<=b.y; y+=ystep ) for ( double x=a.x; x<=b.x; x+=xstep ) { allspot++; if ( !SWWMUtility.PointInPolyobj((x,y),p) ) continue; inspot++; } if ( allspot <= 0 ) return false; // what the fuck? girthitude = (girthitude*inspot)/allspot; // too fucking huge if ( (girthitude > 16777216) || (max(b.z-a.z,max(b.x-a.x,b.y-a.y)) > 1024) ) return false; // not strong enough to bust if ( bust.accdamage < girthitude/300. ) return false; // report bust if ( Instigator && Instigator.player ) { let s = SWWMStats.Find(Instigator.player); if ( s ) s.busts++; SWWMUtility.AchievementProgressInc("bustin",1,Instigator.player); if ( (Instigator is 'Demolitionist') && !Random[DemoLines](0,3) && (int(girthitude**.15) >= 3) ) { if ( gametic > Demolitionist(Instigator).lastbust ) SWWMHandler.AddOneliner("bustkill",2,50); Demolitionist(Instigator).lastbust = gametic+350; } } // call hit fx for devastation sigil (if any) AngeryPower as = instigator?AngeryPower(instigator.FindInventory("AngeryPower")):null; if ( as ) as.DoHitFX(); bust.busted = true; bust.busttics = 0; bust.bustmax = min(30,int(12+girthitude**.1)); // quakin' let q = Actor.Spawn("BustedQuake",(p.LastPos.x,p.LastPos.y,(b.z+a.z)/2)); q.special1 = clamp(int(girthitude**.15),1,9); // "precache" the grid for busting effects bust.boundsmin = a; bust.boundsmax = b; bust.step = (clamp((b.x-a.x)/4.,2.,32.),clamp((b.y-a.y)/4.,2.,32.),clamp((b.z-a.z)/4.,2.,32.)); for ( double y=a.y; y<=b.y; y+=bust.step.y ) for ( double x=a.x; x<=b.x; x+=bust.step.x ) { if ( !SWWMUtility.PointInPolyobj((x,y),p) ) continue; let g = new("BustPoint"); g.pos = (x,y); bust.polygrid.Push(g); } // stop any polyobject movement level.ExecuteSpecial(Polyobj_Stop,instigator,p.StartLine,Line.Front,p.PolyobjectNum); if ( p.Mirror ) level.ExecuteSpecial(Polyobj_Stop,instigator,p.Mirror.StartLine,Line.Front,p.Mirror.PolyobjectNum); // send it to the shadow realm (and ensure it stays there) if ( !p.FindEffector("SWWMBustedPolyobj") ) { let yeet = new("SWWMBustedPolyobj"); yeet.whomstdve = instigator; p.AddEffector(yeet); } bust.SpawnDebris(true); // damnums Vector3 bcenter = (bust.boundsmin+bust.boundsmax)*.5; for ( int i=0; i0); bool bUnbustable = (level.GetUDMFInt(level.UDMF_Sector,hs.Index(),'UNBUSTABLE')>0); // Check if it's a door if ( !swwm_cbtall && (bUnbustable || (!bBustable && !SWWMUtility.IsDoorSector(hs,hp))) ) return false; let ti = ThinkerIterator.Create("BusterWall",STAT_USER); BusterWall iter, bust = null; while ( iter = BusterWall(ti.Next()) ) { if ( (iter.hitsector != hs) || (iter.hitplane != hp) ) continue; bust = iter; break; } bool mnew = false; if ( !bust ) { bust = new("BusterWall"); bust.ChangeStatNum(STAT_USER); bust.hitsector = hs; bust.accdamage = 0; bust.hitplane = hp; bust.bustdir = x; mnew = true; } // multiply damage if ( instigator ) accdamage = instigator.GetModifiedDamage('Wallbust',accdamage,false,instigator,instigator,0); bust.delay = max(bust.delay,5+min(20,accdamage>>4)); bust.accdamage += accdamage; bust.acchits.Push(accdamage); bust.bustdir = (bust.bustdir+x)*.5; double extracut = FRandom[Wallbuster](.01,.04)*bust.accdamage; // is this actually sticking out? double thisheight, othersheight, partheight, cutheight; if ( hp ) { thisheight = hs.FindLowestCeilingPoint(); othersheight = hs.FindHighestCeilingSurrounding(); if ( (thisheight-othersheight) >= 0. ) return false; cutheight = min(hitz+extracut,othersheight+4); } else { thisheight = hs.FindHighestFloorPoint(); othersheight = hs.FindLowestFloorSurrounding(); if ( (thisheight-othersheight) <= 0. ) return false; cutheight = max(hitz-extracut,othersheight-4); } if ( hp ) bust.cutheight = mnew?cutheight:max(bust.cutheight,cutheight); else bust.cutheight = mnew?cutheight:min(bust.cutheight,cutheight); partheight = abs(thisheight-bust.cutheight); // skip if we don't cut off enough if ( partheight <= 0. ) return false; // skip if already busted if ( bust.busted ) return true; // not enough total damage if ( bust.accdamage < 100 ) return false; // estimate sector volume Vector2 a = (32767,32767), b = (-32768,-32768); for ( int i=0; i b.x ) b.x = l.v1.p.x; if ( l.v2.p.x > b.x ) b.x = l.v2.p.x; if ( l.v1.p.y > b.y ) b.y = l.v1.p.y; if ( l.v2.p.y > b.y ) b.y = l.v2.p.y; } double girthitude = (b.x-a.x)*(b.y-a.y)*partheight; // do a grid check to approximate "real" volume, useful for diagonal doors double ystep = (b.y-a.y)/64.; double xstep = (b.x-a.x)/64.; int inspot = 0, allspot = 0; for ( double y=a.y; y<=b.y; y+=ystep ) for ( double x=a.x; x<=b.x; x+=xstep ) { allspot++; if ( level.PointInSector((x,y)) == hs ) inspot++; } if ( allspot <= 0 ) return false; // what the fuck? girthitude = (girthitude*inspot)/allspot; // too fucking huge if ( (girthitude > 16777216) || (max(partheight,max(b.x-a.x,b.y-a.y)) > 1024) ) return false; // not strong enough to bust if ( bust.accdamage < girthitude/300. ) return false; // report bust if ( Instigator && Instigator.player ) { let s = SWWMStats.Find(Instigator.player); if ( s ) s.busts++; SWWMUtility.AchievementProgressInc("bustin",1,Instigator.player); if ( (Instigator is 'Demolitionist') && !Random[DemoLines](0,3) && (int(girthitude**.15) >= 3) ) { if ( gametic > Demolitionist(Instigator).lastbust ) SWWMHandler.AddOneliner("bustkill",2,50); Demolitionist(Instigator).lastbust = gametic+350; } } // call hit fx for devastation sigil (if any) AngeryPower as = instigator?AngeryPower(instigator.FindInventory("AngeryPower")):null; if ( as ) as.DoHitFX(); bust.busted = true; bust.busttics = 0; bust.bustmax = min(30,int(12+girthitude**.1)); // shush hs.flags |= Sector.SECF_SILENTMOVE; // filler texture TextureID rubble = TexMan.CheckForTexture("ASHWALL2"); // equivalents for other iwads if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("ASHWALL"); if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("LOOSERCK"); if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("WASTE03"); if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("textures/DefaultTexture.png"); // a fun little fallback should none of those exist // activate all shoot/use specials (not locked) associated with this sector's two-sided lines for ( int i=0; i