630 lines
21 KiB
Text
630 lines
21 KiB
Text
// Wallbuster effects
|
|
|
|
Class BustedQuake : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
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);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
tics--;
|
|
if ( tics <= 0 ) Destroy();
|
|
}
|
|
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<int> acchits;
|
|
int hitplane;
|
|
bool busted;
|
|
Vector3 bustdir;
|
|
int busttics, delay, bustmax;
|
|
double cutheight;
|
|
// cached
|
|
Vector3 boundsmin, boundsmax, step;
|
|
Array<BustPoint> 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<boundsmax.z; z+=step.z )
|
|
for ( y=boundsmin.y; y<boundsmax.y; y+=step.y )
|
|
for ( x=boundsmin.x; x<boundsmax.x; x+=step.x )
|
|
{
|
|
Vector3 spot = (x,y,z);
|
|
if ( level.PointInSector(spot.xy) != hitsector ) continue;
|
|
spot += (FRandom[Wallbuster](-step.x,step.x),FRandom[Wallbuster](-step.y,step.y),FRandom[Wallbuster](-step.z,step.z));
|
|
if ( !level.IsPointInLevel(spot) ) continue;
|
|
if ( (initial || !(busttics%2)) && !Random[Wallbuster](0,1) )
|
|
{
|
|
Vector3 pvel = (bustdir+SWWMUtility.Vec3FromAngles(FRandom[Wallbuster](0,360),FRandom[Wallbuster](-90,90))).unit()*FRandom[Wallbuster](-2.,8.);
|
|
let s = Actor.Spawn("SWWMHalfSmoke",spot);
|
|
s.vel = pvel;
|
|
s.scale *= 2.5;
|
|
s.special1 = Random[Wallbuster](3,8);
|
|
s.SetShade(Color(1,1,1)*Random[Wallbuster](40,120));
|
|
}
|
|
if ( (!initial && (busttics%3)) || (busttics > (bustmax/2)) ) continue;
|
|
int numpt = Random[Wallbuster](-4,1);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (bustdir+SWWMUtility.Vec3FromAngles(FRandom[Wallbuster](0,360),FRandom[Wallbuster](-90,90))).unit()*FRandom[Wallbuster](9.,24.);
|
|
let s = Actor.Spawn("SWWMSpark",spot);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Wallbuster](0,2);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (bustdir+SWWMUtility.Vec3FromAngles(FRandom[Wallbuster](0,360),FRandom[Wallbuster](-90,90))*.6).unit()*FRandom[Wallbuster](2.,16.);
|
|
let s = Actor.Spawn("SWWMChip",spot);
|
|
s.vel = pvel;
|
|
s.scale *= FRandom[Wallbuster](1.5,3.);
|
|
s.A_SetTranslation('Rubble');
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SpawnDebrisPoly( bool initial = false )
|
|
{
|
|
for ( int i=0; i<polygrid.Size(); i++ ) for ( double z=boundsmin.z; z<boundsmax.z; z+=step.z )
|
|
{
|
|
Vector3 spot = (polygrid[i].pos.x,polygrid[i].pos.y,z);
|
|
spot += (FRandom[Wallbuster](-step.x,step.x),FRandom[Wallbuster](-step.y,step.y),FRandom[Wallbuster](-step.z,step.z));
|
|
if ( !level.IsPointInLevel(spot) ) continue;
|
|
if ( (initial || !(busttics%2)) && !Random[Wallbuster](0,1) )
|
|
{
|
|
Vector3 pvel = (bustdir+SWWMUtility.Vec3FromAngles(FRandom[Wallbuster](0,360),FRandom[Wallbuster](-90,90))).unit()*FRandom[Wallbuster](-2.,8.);
|
|
let s = Actor.Spawn("SWWMHalfSmoke",spot);
|
|
s.vel = pvel;
|
|
s.scale *= 2.5;
|
|
s.special1 = Random[Wallbuster](3,8);
|
|
s.SetShade(Color(1,1,1)*Random[Wallbuster](40,120));
|
|
}
|
|
if ( (!initial && (busttics%3)) || (busttics > (bustmax/2)) ) continue;
|
|
int numpt = Random[Wallbuster](-4,1);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (bustdir+SWWMUtility.Vec3FromAngles(FRandom[Wallbuster](0,360),FRandom[Wallbuster](-90,90))).unit()*FRandom[Wallbuster](9.,24.);
|
|
let s = Actor.Spawn("SWWMSpark",spot);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Wallbuster](0,2);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (bustdir+SWWMUtility.Vec3FromAngles(FRandom[Wallbuster](0,360),FRandom[Wallbuster](-90,90))*.6).unit()*FRandom[Wallbuster](2.,16.);
|
|
let s = Actor.Spawn("SWWMChip",spot);
|
|
s.vel = pvel;
|
|
s.scale *= FRandom[Wallbuster](1.5,3.);
|
|
s.A_SetTranslation('Rubble');
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsIOSWall( Line l )
|
|
{
|
|
TextureID facetex[9];
|
|
facetex[0] = TexMan.CheckForTexture("ZZZFACE1",TexMan.Type_Wall);
|
|
facetex[1] = TexMan.CheckForTexture("ZZZFACE2",TexMan.Type_Wall);
|
|
facetex[2] = TexMan.CheckForTexture("ZZZFACE3",TexMan.Type_Wall);
|
|
facetex[3] = TexMan.CheckForTexture("ZZZFACE4",TexMan.Type_Wall);
|
|
facetex[4] = TexMan.CheckForTexture("ZZZFACE5",TexMan.Type_Wall);
|
|
facetex[5] = TexMan.CheckForTexture("DBRAIN1",TexMan.Type_Wall);
|
|
facetex[6] = TexMan.CheckForTexture("DBRAIN2",TexMan.Type_Wall);
|
|
facetex[7] = TexMan.CheckForTexture("DBRAIN3",TexMan.Type_Wall);
|
|
facetex[8] = TexMan.CheckForTexture("DBRAIN4",TexMan.Type_Wall);
|
|
for ( int i=0; i<9; i++ )
|
|
{
|
|
for ( int j=0; j<3; j++ )
|
|
{
|
|
if ( l.sidedef[0].GetTexture(j) == facetex[i] ) return true;
|
|
if ( l.sidedef[1] && l.sidedef[1].GetTexture(j) == facetex[i] ) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool ProjectileBust( Actor a, int accdamage, Vector3 x )
|
|
{
|
|
LineTracer faketracer = new("LineTracer");
|
|
F3DFloor ff;
|
|
Vector3 HitNormal;
|
|
if ( a.BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<a.BlockingFloor.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(a.BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(a.BlockingFloor.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
|
|
ff = a.BlockingFloor.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff )
|
|
{
|
|
faketracer.Results.ffloor = ff;
|
|
HitNormal = -ff.top.Normal;
|
|
}
|
|
else HitNormal = a.BlockingFloor.floorplane.Normal;
|
|
faketracer.Results.HitType = TRACE_HitFloor;
|
|
faketracer.Results.HitSector = a.BlockingFloor;
|
|
}
|
|
else if ( a.BlockingCeiling )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<a.BlockingCeiling.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(a.BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(a.BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(a.pos.xy) ~== a.ceilingz) ) continue;
|
|
ff = a.BlockingCeiling.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff )
|
|
{
|
|
faketracer.Results.ffloor = ff;
|
|
HitNormal = -ff.bottom.Normal;
|
|
}
|
|
else HitNormal = a.BlockingCeiling.ceilingplane.Normal;
|
|
faketracer.Results.HitType = TRACE_HitCeiling;
|
|
faketracer.Results.HitSector = a.BlockingCeiling;
|
|
}
|
|
else if ( a.BlockingLine )
|
|
{
|
|
HitNormal = (-a.BlockingLine.delta.y,a.BlockingLine.delta.x,0).unit();
|
|
int wside = SWWMUtility.PointOnLineSide(a.pos.xy,a.BlockingLine);
|
|
if ( !wside ) HitNormal *= -1;
|
|
faketracer.Results.HitType = TRACE_HitWall;
|
|
faketracer.Results.HitLine = a.BlockingLine;
|
|
faketracer.Results.Side = wside;
|
|
faketracer.Results.Tier = TIER_Middle;
|
|
// guess the tier hit
|
|
if ( a.BlockingLine.sidedef[1] )
|
|
{
|
|
double ceil = a.BlockingLine.sidedef[!wside].sector.ceilingplane.ZAtPoint(a.pos.xy);
|
|
double flor = a.BlockingLine.sidedef[!wside].sector.floorplane.ZAtPoint(a.pos.xy);
|
|
if ( a.pos.z >= 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<p.lines.Size(); i++ )
|
|
{
|
|
Line l = p.lines[i];
|
|
if ( l.v1.p.x < a.x ) a.x = l.v1.p.x;
|
|
if ( l.v2.p.x < a.x ) a.x = l.v2.p.x;
|
|
if ( l.v1.p.y < a.y ) a.y = l.v1.p.y;
|
|
if ( l.v2.p.y < a.y ) a.y = l.v2.p.y;
|
|
if ( l.v1.p.x > 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) )
|
|
SWWMHandler.AddOneliner("bustkill",2,50);
|
|
}
|
|
// 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; i<bust.acchits.Size(); i++ )
|
|
SWWMDamNum.Spawn(bust.acchits[i],level.Vec3Offset(bcenter,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.),'Wallbust');
|
|
return true;
|
|
}
|
|
|
|
static bool Bust( TraceResults d, int accdamage, Actor instigator, Vector3 x, double hitz )
|
|
{
|
|
// we can't blow up 3D floors
|
|
if ( d.ffloor ) return false;
|
|
Sector hs = d.HitSector;
|
|
int hp;
|
|
if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
// check if it's a polyobject line, if so, switch to the other bust method
|
|
swwm_PolyObjectHandle p;
|
|
if ( SWWMUtility.IsPolyLine(d.HitLine,p) ) return BustPolyobj(p,accdamage,instigator,x);
|
|
// no busting the goat
|
|
if ( IsIOSWall(d.HitLine) ) return false;
|
|
// onesided wall? no bust
|
|
if ( !d.HitLine.sidedef[1] ) return false;
|
|
// sector is opposite of side hit
|
|
hs = d.HitLine.sidedef[!d.Side].sector;
|
|
// what part we hit?
|
|
if ( d.Tier == TIER_Upper ) hp = 1; // ceiling
|
|
else if ( d.Tier == TIER_Lower ) hp = 0; // floor
|
|
else return false; // middle ignored
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
// no busting the goat
|
|
for ( int i=0; i<hs.lines.Size(); i++ )
|
|
{
|
|
if( IsIOSWall(hs.lines[i]) ) return false;
|
|
}
|
|
hp = 1;
|
|
}
|
|
else if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
// no busting the goat
|
|
for ( int i=0; i<hs.lines.Size(); i++ )
|
|
{
|
|
if( IsIOSWall(hs.lines[i]) ) return false;
|
|
}
|
|
hp = 0;
|
|
}
|
|
else return false; // this isn't a valid hit, needs to be world geometry
|
|
// Check if it's a door
|
|
if ( !swwm_cbtall && !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<hs.lines.Size(); i++ )
|
|
{
|
|
Line l = hs.lines[i];
|
|
if ( l.v1.p.x < a.x ) a.x = l.v1.p.x;
|
|
if ( l.v2.p.x < a.x ) a.x = l.v2.p.x;
|
|
if ( l.v1.p.y < a.y ) a.y = l.v1.p.y;
|
|
if ( l.v2.p.y < a.y ) a.y = l.v2.p.y;
|
|
if ( l.v1.p.x > 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) )
|
|
SWWMHandler.AddOneliner("bustkill",2,50);
|
|
}
|
|
// 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",TexMan.Type_Any);
|
|
// equivalents for other iwads
|
|
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("ASHWALL",TexMan.Type_Any);
|
|
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("LOOSERCK",TexMan.Type_Any);
|
|
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("WASTE03",TexMan.Type_Any);
|
|
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("-noflat-",TexMan.Type_Any); // an unfortunate 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<hs.Lines.Size(); i++ )
|
|
{
|
|
Line l = hs.Lines[i];
|
|
int locknum = SWWMUtility.GetLineLock(l);
|
|
if ( locknum && (!instigator || !instigator.CheckKeys(locknum,false,true)) ) continue;
|
|
if ( !l.sidedef[1] ) continue;
|
|
int away = 0;
|
|
if ( l.sidedef[0].sector == hs ) away = 1;
|
|
l.Activate(instigator,away,SPAC_Use);
|
|
l.Activate(instigator,away,SPAC_Impact);
|
|
}
|
|
// if this is a broken crusher, we need to clear that now that we've busted it
|
|
SWWMCrusherBroken.Remove(hp?null:hs,hp?hs:null);
|
|
// quakin'
|
|
let q = Actor.Spawn("BustedQuake",(hs.centerspot.x,hs.centerspot.y,thisheight));
|
|
q.special1 = clamp(int(girthitude**.15),1,9);
|
|
if ( hp )
|
|
{
|
|
// remove any current movers
|
|
if ( hs.CeilingData ) hs.CeilingData.Destroy();
|
|
// blow up that ceiling
|
|
hs.MoveCeiling(abs(partheight),bust.cutheight,0,1,false);
|
|
bust.boundsmin = (a.x,a.y,thisheight)+(1,1,1);
|
|
bust.boundsmax = (b.x,b.y,bust.cutheight)-(1,1,1);
|
|
// prevent any further ceiling movement
|
|
level.CreateCeiling(hs,Ceiling.ceilRaiseByValue,null,0.,0.,1.);
|
|
hs.StopSoundSequence(CHAN_VOICE);
|
|
}
|
|
else
|
|
{
|
|
// remove any current movers
|
|
if ( hs.FloorData ) hs.FloorData.Destroy();
|
|
// blow up that floor
|
|
hs.MoveFloor(abs(partheight),abs(bust.cutheight),0,-1,false,true);
|
|
bust.boundsmin = (a.x,a.y,bust.cutheight)+(1,1,1);
|
|
bust.boundsmax = (b.x,b.y,thisheight)-(1,1,1);
|
|
// prevent any further floor movement
|
|
level.CreateFloor(hs,Floor.floorLowerByValue,null,0.,1.);
|
|
hs.StopSoundSequence(CHAN_WEAPON);
|
|
}
|
|
bust.step = (clamp((b.x-a.x)/4.,2.,32.),clamp((b.y-a.y)/4.,2.,32.),clamp(partheight/4.,2.,32.));
|
|
bust.SpawnDebris(true);
|
|
hs.SetTexture(hp,rubble);
|
|
hs.SetXScale(hp,1.);
|
|
hs.SetYScale(hp,1.);
|
|
hs.SetAngle(hp,0.);
|
|
for ( int i=0; i<hs.Lines.Size(); i++ )
|
|
{
|
|
Line l = hs.Lines[i];
|
|
if ( !l.sidedef[1] )
|
|
{
|
|
if ( hp && !(l.flags&Line.ML_DONTPEGBOTTOM) )
|
|
l.sidedef[0].AddTextureYOffset(1,-partheight); // shift down
|
|
else if ( !hp && (l.flags&Line.ML_DONTPEGBOTTOM) )
|
|
l.sidedef[0].AddTextureYOffset(1,partheight); // shift up
|
|
continue;
|
|
}
|
|
int away = 0;
|
|
if ( l.sidedef[0].sector == hs ) away = 1;
|
|
for ( int j=0; j<2; j++ )
|
|
{
|
|
if ( l.sidedef[j].GetTexture(hp?0:2).IsValid() )
|
|
{
|
|
if ( j == away )
|
|
{
|
|
if ( hp && !(l.flags&Line.ML_DONTPEGTOP) )
|
|
l.sidedef[j].AddTextureYOffset(0,-partheight); // shift down
|
|
else if ( !hp && !(l.flags&Line.ML_DONTPEGBOTTOM) )
|
|
l.sidedef[j].AddTextureYOffset(2,partheight); // shift up
|
|
}
|
|
}
|
|
else
|
|
{
|
|
l.sidedef[j].SetTexture(hp?0:2,rubble);
|
|
l.sidedef[j].SetTextureXScale(hp?0:2,1.);
|
|
l.sidedef[j].SetTextureYScale(hp?0:2,1.);
|
|
}
|
|
}
|
|
}
|
|
// damnums
|
|
Vector3 bcenter = (bust.boundsmin+bust.boundsmax)*.5;
|
|
for ( int i=0; i<bust.acchits.Size(); i++ )
|
|
SWWMDamNum.Spawn(bust.acchits[i],level.Vec3Offset(bcenter,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.),'Wallbust');
|
|
return true;
|
|
}
|
|
}
|