From 8094c98fe540cc3700efdf5bb89e2960157b2d28 Mon Sep 17 00:00:00 2001 From: Marisa Kirisame Date: Tue, 28 Sep 2021 18:43:27 +0200 Subject: [PATCH] Implement polyobject busting. --- language.version | 4 +- zscript/swwm_Polyobjects/PolyobjectHandle.zs | 3 + .../PolyobjectMapPostprocessor.zs | 39 ++++ zscript/swwm_player.zsc | 48 ++++- zscript/swwm_thinkers.zsc | 16 ++ zscript/utility/swwm_utility.zsc | 54 +++++- zscript/weapons/swwm_cbt_fx.zsc | 167 +++++++++++++++++- 7 files changed, 323 insertions(+), 8 deletions(-) diff --git a/language.version b/language.version index 23060b883..ab90ce7a5 100644 --- a/language.version +++ b/language.version @@ -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-"; diff --git a/zscript/swwm_Polyobjects/PolyobjectHandle.zs b/zscript/swwm_Polyobjects/PolyobjectHandle.zs index 6898ef040..23383f231 100644 --- a/zscript/swwm_Polyobjects/PolyobjectHandle.zs +++ b/zscript/swwm_Polyobjects/PolyobjectHandle.zs @@ -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 Lines; + // Initial angle of StartLine double StartAngle; diff --git a/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs b/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs index 4259f8e76..13f78632f 100644 --- a/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs +++ b/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs @@ -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); } } } diff --git a/zscript/swwm_player.zsc b/zscript/swwm_player.zsc index fd0cac76f..3a0ba8242 100644 --- a/zscript/swwm_player.zsc +++ b/zscript/swwm_player.zsc @@ -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 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= 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' ) { diff --git a/zscript/swwm_thinkers.zsc b/zscript/swwm_thinkers.zsc index d217566a0..f549654b1 100644 --- a/zscript/swwm_thinkers.zsc +++ b/zscript/swwm_thinkers.zsc @@ -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 { diff --git a/zscript/utility/swwm_utility.zsc b/zscript/utility/swwm_utility.zsc index c4ffdf0fb..59d92e557 100644 --- a/zscript/utility/swwm_utility.zsc +++ b/zscript/utility/swwm_utility.zsc @@ -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 acchits; int hitplane; @@ -54,6 +60,7 @@ Class BusterWall : Thinker double cutheight; // cached Vector3 boundsmin, boundsmax, step; + Array 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 (bustmax/2)) ) continue; + int numpt = Random[Wallbuster](-4,1); + for ( int i=0; i>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('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