Implement polyobject busting.

This commit is contained in:
Mari the Deer 2021-09-28 18:43:27 +02:00
commit 8094c98fe5
7 changed files with 323 additions and 8 deletions

View file

@ -1,3 +1,3 @@
[default]
SWWM_MODVER="\chSWWM \czGZ\c- \cw1.1.10 r2 \cu(Tue 28 Sep 15:54:58 CEST 2021)\c-";
SWWM_SHORTVER="\cw1.1.10 r2 \cu(2021-09-28 15:54:58)\c-";
SWWM_MODVER="\chSWWM \czGZ\c- \cw1.1.10 r2 \cu(Tue 28 Sep 18:43:27 CEST 2021)\c-";
SWWM_SHORTVER="\cw1.1.10 r2 \cu(2021-09-28 18:43:27)\c-";

View file

@ -16,6 +16,9 @@ class swwm_PolyobjectHandle: Thinker
// Line defining the polyobject (Polyobj_StartLine, or one of Polyobj_ExplicitLine)
Line StartLine;
// [MK] All lines belonging to the polyobject
Array<Line> Lines;
// Initial angle of StartLine
double StartAngle;

View file

@ -15,6 +15,12 @@ class swwm_PolyobjectHandlePostProcessor: LevelPostProcessor
{
// Ignore every thing that isn't a Polyobject StartSpot
int ednum = GetThingEdNum(i);
// [MK] hotfix for this to recognize hexen polyobjects
if (gameinfo.gametype&GAME_Hexen)
{
if (ednum == 3001) ednum = swwm_PolyobjectHandle.POTYP_NORMAL;
else if (ednum == 3002) ednum = swwm_PolyobjectHandle.POTYP_CRUSH;
}
if (ednum < swwm_PolyobjectHandle.POTYP_NORMAL || ednum > swwm_PolyobjectHandle.POTYP_HURT)
continue;
@ -79,6 +85,39 @@ class swwm_PolyobjectHandlePostProcessor: LevelPostProcessor
// Store the line
handle.StartLine = line;
// [MK] the library doesn't store ALL lines belonging to the polyobject, but we need them
handle.Lines.Push(line);
// [MK] collect all connected lines if this is Polyobj_StartLine
if ( line.Special != Polyobj_StartLine )
continue;
bool newlines;
do
{
newlines = false;
for (int j = 0; j < Level.Lines.Size(); j++)
{
Line linea = Level.Lines[j];
if (handle.Lines.Find(linea) < handle.Lines.Size())
continue;
bool nomatches = true;
for (int k = 0; k < handle.Lines.Size(); k++)
{
Line lineb = handle.Lines[k];
if ((linea.v1 != lineb.v1) && (linea.v1 != lineb.v2) && (linea.v2 != lineb.v1) && (linea.v2 != lineb.v2))
continue;
nomatches = false;
break;
}
if (nomatches)
continue;
newlines = true;
handle.Lines.Push(linea);
}
}
while (newlines);
}
}
}

View file

@ -1579,6 +1579,47 @@ Class Demolitionist : PlayerPawn
}
SWWMUtility.MarkAchievement('swwm_achievement_crush',player);
}
private void CheckBreakPolyobject( int dmg )
{
// see if there are any crushing polyobjects currently "encroaching" the player
Array<Line> touching;
BlockLinesIterator bl = BlockLinesIterator.Create(self,radius+8);
double tbox[4];
// top, bottom, left, right
tbox[0] = pos.y+(radius+8);
tbox[1] = pos.y-(radius+8);
tbox[2] = pos.x-(radius+8);
tbox[3] = pos.x+(radius+8);
while ( bl.Next() )
{
Line l = bl.CurLine;
if ( !l ) continue;
if ( tbox[2] > l.bbox[3] ) continue;
if ( tbox[3] < l.bbox[2] ) continue;
if ( tbox[0] < l.bbox[1] ) continue;
if ( tbox[1] > l.bbox[0] ) continue;
if ( SWWMUtility.BoxOnLineSide(tbox[0],tbox[1],tbox[2],tbox[3],l) != -1 ) continue;
touching.Push(l);
}
let pi = swwm_PolyobjectIterator.Create();
swwm_PolyobjectHandle p;
while ( p = pi.Next() )
{
if ( (p.Type != swwm_PolyobjectHandle.POTYP_CRUSH) && (p.Type != swwm_PolyobjectHandle.POTYP_HURT) )
continue;
for ( int i=0; i<touching.Size(); i++ )
{
if ( p.Lines.Find(touching[i]) >= p.Lines.Size() ) continue;
Vector2 diragainst = pos.xy-p.GetPos();
double vsiz = diragainst.length();
if ( vsiz > 0 ) diragainst /= vsiz;
if ( BusterWall.BustPolyobj(p,max(dmg,(100-health)*5),self,(diragainst.x,diragainst.y,0)) )
SWWMUtility.MarkAchievement('swwm_achievement_crush',player);
if ( p.Mirror && BusterWall.BustPolyobj(p.Mirror,max(dmg,(100-health)*5),self,-(diragainst.x,diragainst.y,0)) )
SWWMUtility.MarkAchievement('swwm_achievement_crush',player);
}
}
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// we still have to ENSURE ENTIRELY that this gets nullified (TELEFRAG_DAMAGE overrides damage factors somehow)
@ -1592,7 +1633,12 @@ Class Demolitionist : PlayerPawn
if ( mod == 'Crush' )
{
// check if we can break any active crushers
if ( !inflictor && !source ) CheckBreakCrusher();
// (or polyobjects)
if ( !inflictor && !source )
{
CheckBreakCrusher();
CheckBreakPolyobject(damage);
}
// break a spike trap
else if ( source is 'ThrustFloor' )
{

View file

@ -151,6 +151,22 @@ Class SWWMDamageAccumulator : Thinker
}
}
// ensures a polyobj stays out of bounds FOREVER
Class SWWMBustedPolyobj : swwm_PolyobjectEffector
{
Actor whomstdve;
override void PolyTick()
{
if ( Polyobject.GetPos() == (32000,32000) ) return;
double dist = (Polyobject.GetPos()-(32000,32000)).length();
Level.ExecuteSpecial(Polyobj_Stop,whomstdve,Polyobject.StartLine,Line.Front,Polyobject.PolyobjectNum);
if ( Polyobject.Mirror ) Level.ExecuteSpecial(Polyobj_Stop,whomstdve,Polyobject.Mirror.StartLine,Line.Front,Polyobject.Mirror.PolyobjectNum);
Level.ExecuteSpecial(Polyobj_MoveTo,whomstdve,Polyobject.StartLine,Line.Front,Polyobject.PolyobjectNum,int(dist*8),32000,32000);
if ( Polyobject.Mirror ) Level.ExecuteSpecial(Polyobj_Stop,whomstdve,Polyobject.Mirror.StartLine,Line.Front,Polyobject.Mirror.PolyobjectNum);
}
}
// prevents floors/ceilings from ever moving again, as they're "broken crushers"
Class SWWMCrusherBroken : Thinker
{

View file

@ -1741,12 +1741,62 @@ Class SWWMUtility
return false, checkme;
}
static bool IsPolyLine( Line l, out swwm_PolyobjectHandle p )
// iterate through polyobjects and see if this line is part of one (returning which, if any)
static bool IsPolyLine( Line l, out swwm_PolyobjectHandle o )
{
// TODO iterate through polyobjects and see if this line is connected to their start line
let pi = swwm_PolyobjectIterator.Create();
swwm_PolyobjectHandle p;
while ( p = pi.Next() )
{
if ( p.Lines.Find(l) >= p.Lines.Size() ) continue;
o = p;
return true;
}
o = null;
return false;
}
// checks if the specified world coordinate is inside the polyobject
// this check is very naive but it should handle most "normal" shapes
// (yeah, sorry if you somehow want to play this mod with lilith.pk3)
static bool PointInPolyobj( Vector2 p, swwm_PolyobjectHandle o )
{
// first pass, find which vertex out of all lines is closest
Vertex v = o.StartLine.v1;
double dist = (v.p-p).length();
for ( int i=0; i<o.Lines.Size(); i++ )
{
Line l = o.Lines[i];
double dist2 = (l.v1.p-p).length();
if ( dist2 < dist )
{
v = l.v1;
dist = dist2;
}
dist2 = (l.v2.p-p).length();
if ( dist2 < dist )
{
v = l.v2;
dist = dist2;
}
}
// second pass, find which two lines share that vertex
// (in theory there should only be two)
Line a = null, b = null;
for ( int i=0; i<o.Lines.Size(); i++ )
{
Line l = o.Lines[i];
if ( (l.v1 == v) || (l.v2 == v) )
{
if ( !a ) a = l;
else if ( !b ) b = l;
else break;
}
}
// is the point behind both lines?
return (PointOnLineSide(p,a) && PointOnLineSide(p,b));
}
// full reset of inventory (excluding collectibles, and optionally resetting the score)
static play void WipeInventory( Actor mo, bool resetscore = false, bool allplayers = false )
{

View file

@ -41,10 +41,16 @@ Class BustedQuake : Actor
}
}
Class BustPoint
{
Vector2 pos;
}
// Bustin' makes me feel good
Class BusterWall : Thinker
{
Sector hitsector;
swwm_PolyobjectHandle hitpoly;
int accdamage;
Array<int> acchits;
int hitplane;
@ -54,6 +60,7 @@ Class BusterWall : Thinker
double cutheight;
// cached
Vector3 boundsmin, boundsmax, step;
Array<BustPoint> polygrid;
override void Tick()
{
@ -84,6 +91,11 @@ Class BusterWall : Thinker
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 )
@ -122,6 +134,42 @@ Class BusterWall : Thinker
}
}
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+(FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.))).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+(FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.))).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+(FRandom[Wallbuster](-.6,.6),FRandom[Wallbuster](-.6,.6),FRandom[Wallbuster](-.6,.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];
@ -227,9 +275,122 @@ Class BusterWall : Thinker
return Bust(faketracer.Results,accdamage,instigator,x,hitz);
}
private static bool BustPolyobj( swwm_PolyobjectHandle p, int accdamage, Actor instigator, Vector3 x, double hitz )
static bool BustPolyobj( swwm_PolyobjectHandle p, int accdamage, Actor instigator, Vector3 x )
{
return false;
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('swwm_progress_bustin',1,Instigator.player);
}
// 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;
if ( swwm_accdamage )
SWWMScoreObj.Spawn(-bust.accdamage,level.Vec3Offset(bcenter,(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8))),ST_Damage);
else for ( int i=0; i<bust.acchits.Size(); i++ )
SWWMScoreObj.Spawn(-bust.acchits[i],level.Vec3Offset(bcenter,(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8))),ST_Damage);
return true;
}
static bool Bust( TraceResults d, int accdamage, Actor instigator, Vector3 x, double hitz )
@ -242,7 +403,7 @@ Class BusterWall : Thinker
{
// 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,hitz);
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