- Try to get rid of all implicit casts from string to name, color or class. - Use FindClass where needed. - Used a map in a case where a dictionary was unneeded. - Use new bounce flags where needed. - Replace Legacy of Rust weapons/ammo.
618 lines
21 KiB
Text
618 lines
21 KiB
Text
// Wallbuster effects
|
|
|
|
Class BustedQuake : SWWMNonInteractiveActor
|
|
{
|
|
override void PostBeginPlay()
|
|
{
|
|
if ( (specialf1 < 3) || (specialf1 > 6) )
|
|
{
|
|
A_StartSound("wallbuster/smallbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,specialf1*.32),1./max(1.,specialf1*.35),1.-specialf1*.05);
|
|
A_StartSound("wallbuster/smallbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,specialf1*.32),1./max(1.,specialf1*.35),1.-specialf1*.05);
|
|
}
|
|
if ( specialf1 >= 3 )
|
|
{
|
|
A_StartSound("wallbuster/bigbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,specialf1*.35),1./max(1.,specialf1*.35),1.-specialf1*.01);
|
|
A_StartSound("wallbuster/bigbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,specialf1*.35),1./max(1.,specialf1*.35),1.-specialf1*.01);
|
|
}
|
|
A_QuakeEx(specialf1,specialf1,specialf1,20+int(specialf1*5),0,300+int(specialf1*90),"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:specialf1*.1);
|
|
A_AlertMonsters(swwm_uncapalert?0:2500,AMF_EMITFROMTARGET);
|
|
}
|
|
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 = SWWMAnimSprite.SpawnAt('SWWMHalfSmoke',spot);
|
|
s.vel = pvel;
|
|
s.scale *= 2.5;
|
|
s.framestep = Random[Wallbuster](4,9);
|
|
s.scolor = 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 )
|
|
{
|
|
foreach ( g:polygrid ) for ( double z=boundsmin.z; z<boundsmax.z; z+=step.z )
|
|
{
|
|
Vector3 spot = (g.pos.x,g.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 = SWWMAnimSprite.SpawnAt('SWWMHalfSmoke',spot);
|
|
s.vel = pvel;
|
|
s.scale *= 2.5;
|
|
s.framestep = Random[Wallbuster](4,9);
|
|
s.scolor = 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 = (0,0,0);
|
|
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 && SWWMUtility.BlockingLineIsBlocking(a,Line.ML_BLOCKEVERYTHING|Line.ML_BLOCKPROJECTILE,a.BlockingLine) )
|
|
{
|
|
HitNormal = (-a.BlockingLine.delta.y,a.BlockingLine.delta.x,0).unit();
|
|
int wside = Level.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 return false; // nothing busted
|
|
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);
|
|
foreach ( l:p.Lines )
|
|
{
|
|
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; // huh???
|
|
girthitude = (girthitude*inspot)/allspot;
|
|
// too 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.specialf1 = clamp(girthitude**.15,0.,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;
|
|
foreach ( hit:bust.acchits )
|
|
SWWMDamNum.Spawn(hit,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
|
|
foreach ( l:hs.Lines )
|
|
{
|
|
if( IsIOSWall(l) )
|
|
return false;
|
|
}
|
|
hp = 1;
|
|
}
|
|
else if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
// no busting the goat
|
|
foreach ( l:hs.Lines )
|
|
{
|
|
if( IsIOSWall(l) )
|
|
return false;
|
|
}
|
|
hp = 0;
|
|
}
|
|
else return false; // this isn't a valid hit, needs to be world geometry
|
|
bool bBustable = (level.GetUDMFInt(level.UDMF_Sector,hs.Index(),'BUSTABLE')>0);
|
|
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);
|
|
foreach ( l:hs.Lines )
|
|
{
|
|
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; // huh???
|
|
girthitude = (girthitude*inspot)/allspot;
|
|
// too 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
|
|
foreach ( l:hs.Lines )
|
|
{
|
|
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.specialf1 = 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.);
|
|
foreach ( l:hs.Lines )
|
|
{
|
|
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;
|
|
foreach( hit:bust.acchits )
|
|
SWWMDamNum.Spawn(hit,level.Vec3Offset(bcenter,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.),'Wallbust');
|
|
return true;
|
|
}
|
|
}
|