From d532ddc3fa103eda8a2a4632022bc9187cf12181 Mon Sep 17 00:00:00 2001 From: Marisa Kirisame Date: Tue, 28 Sep 2021 15:50:07 +0200 Subject: [PATCH] More robust wall busting / crusher breaking. Integrate ZPolyobject library (will be used soon). Update credits. --- credits.txt | 1 + language.def_menu | 1 + language.es_menu | 1 + language.version | 4 +- zscript.txt | 2 + zscript/items/swwm_powerups.zsc | 3 +- zscript/menu/swwm_credits.zsc | 6 +- .../swwm_Polyobjects/PolyobjectEffector.zs | 48 +++ zscript/swwm_Polyobjects/PolyobjectHandle.zs | 356 ++++++++++++++++++ .../PolyobjectMapPostprocessor.zs | 84 +++++ zscript/swwm_Polyobjects/Polyobjects.zs | 3 + zscript/swwm_thinkers.zsc | 41 +- zscript/utility/swwm_utility.zsc | 6 + zscript/weapons/swwm_cbt_fx.zsc | 39 +- 14 files changed, 557 insertions(+), 38 deletions(-) create mode 100644 zscript/swwm_Polyobjects/PolyobjectEffector.zs create mode 100644 zscript/swwm_Polyobjects/PolyobjectHandle.zs create mode 100644 zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs create mode 100644 zscript/swwm_Polyobjects/Polyobjects.zs diff --git a/credits.txt b/credits.txt index 2f4a401ed..c21bc0d64 100644 --- a/credits.txt +++ b/credits.txt @@ -19,6 +19,7 @@ Most of the work here is original, but there are some notable exceptions: - In addition, a whole lot of stock sounds and internet meme sounds have also been used. - Some sprites and sounds are taken from (shareware) Wolfenstein 3D. - This mod uses Gutamatics, by Gutawer. Big thanks. + - This mod uses Mikolah's ZPolyobject library. Many thanks too. - Title theme, "Traumatic State", by Teque (which a lot of people just know as "the AS-Golgotha music"). - Intermission theme, "Dragony", also by Teque (very comfy music considering the rest of his repertoire). - Startup/credits theme, "Hidden Tune #242", also by Teque too (super comfy music, ideal for this use). diff --git a/language.def_menu b/language.def_menu index 650b92e38..d85c5fee6 100644 --- a/language.def_menu +++ b/language.def_menu @@ -414,6 +414,7 @@ SWWM_CMAB2 = "For being an amazing friend who believes in me, and for inspiring SWWM_CDRAGON2 = "For being a good pet dragon who cares about me."; SWWM_CLUCY2 = "For the Tewi font, which I've used for many many years. I hope you're doing well, wherever you are."; SWWM_CGUTA2 = "For the Gutamatics library, and for helping me with learning ZScript."; +SWWM_CMIKO2 = "For the VERY useful ZPolyobject library."; SWWM_CKEKS2 = "For assistance with exception handling code, and also for being such a cool Touhou nerd."; SWWM_CZN2 = "For slope alignment code, and to Nash also for being a cool smart cactus dude."; SWWM_CVAL2 = "For the custom Nashgore footprints, and for being a good friend and cute bun."; diff --git a/language.es_menu b/language.es_menu index c3bbf458d..100d92816 100644 --- a/language.es_menu +++ b/language.es_menu @@ -411,6 +411,7 @@ SWWM_CMAB2 = "Por ser una grandísima amiga que cree en mí, y por inspirarme a SWWM_CDRAGON2 = "Por ser un buen dragón mascota que se preocupa por mí."; SWWM_CLUCY2 = "Por la fuente Tewi, que he seguido usando todos estos años. Espero que estés donde estés, te encuentres bien."; SWWM_CGUTA2 = "Por la librería de Gutamatics, y por ayudarme a aprender ZScript."; +SWWM_CMIKO2 = "Por la MUY útil librería de ZPolyobject."; SWWM_CKEKS2 = "Por asistencia con el código de manejo de excepciones, y también por ser un friki tan guay de Touhou."; SWWM_CZN2 = "Por el código para alineación con superficies inclinadas, y a Nash además por ser un molón tío cactus listo."; SWWM_CVAL2 = "Por las huellas personalizadas para Nashgore, y por ser una buena amiga y conejita mona."; diff --git a/language.version b/language.version index cc7d02790..23060b883 100644 --- a/language.version +++ b/language.version @@ -1,3 +1,3 @@ [default] -SWWM_MODVER="\chSWWM \czGZ\c- \cw1.1.10 \cu(Tue 28 Sep 14:26:51 CEST 2021)\c-"; -SWWM_SHORTVER="\cw1.1.10 \cu(2021-09-28 14:26:51)\c-"; +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-"; diff --git a/zscript.txt b/zscript.txt index fa756fb2e..212aaa936 100644 --- a/zscript.txt +++ b/zscript.txt @@ -9,6 +9,8 @@ version "4.6" // Gutamatics #include "zscript/swwm_Gutamatics/Include.zsc" +// ZPolyobject +#include "zscript/swwm_Polyobjects/Polyobjects.zs" // utility code #include "zscript/utility/swwm_coordutil.zsc" #include "zscript/utility/swwm_utility.zsc" diff --git a/zscript/items/swwm_powerups.zsc b/zscript/items/swwm_powerups.zsc index 0dd2f8a3f..89aa3a2a0 100644 --- a/zscript/items/swwm_powerups.zsc +++ b/zscript/items/swwm_powerups.zsc @@ -3301,7 +3301,8 @@ Class AngeryPower : Powerup // (2^31-1)/25 : guarantee that it caps rather than overflowing if ( damage > 85899345 ) newdamage = int.max; else newdamage = damage*25; - DoHitFX(); + // don't play hit fx for wall busting, as it'll be done manually if the bust goes through + if ( damageType != 'Wallbust' ) DoHitFX(); } } Class AngerySigil : Inventory diff --git a/zscript/menu/swwm_credits.zsc b/zscript/menu/swwm_credits.zsc index ede5d1e33..a715c0d71 100644 --- a/zscript/menu/swwm_credits.zsc +++ b/zscript/menu/swwm_credits.zsc @@ -184,18 +184,15 @@ Class SWWMCreditsMenu : GenericMenu cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Dac")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Pietro Gagliardi")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Ryan Weatherman")); - cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Valerie Thiessen")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Xada Xephron")); - cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Zard1084")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"John")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"VoanHead")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"NekoMithos")); - cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Artem Bashev")); + cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Ceyne Taikato")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"bouncytem")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Brett Saltzer")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Clint Walker")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Figo")); - cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Holly_Rook")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"m8f")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"Namsan")); cpatrons.Push(new("SWWMCreditsEntry").Init(sfnt,"YaGirlJuniper")); @@ -203,6 +200,7 @@ Class SWWMCreditsMenu : GenericMenu cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"KynikossDragonn","$SWWM_CDRAGON2")); cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"Lucy","$SWWM_CLUCY2")); cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"Gutawer","$SWWM_CGUTA2")); + cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"Mikolah","$SWWM_CMIKO2")); cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"KeksDose","$SWWM_CKEKS2")); cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"ZZYZX & Nash","$SWWM_CZN2")); cthanks.Push(new("SWWMCreditsEntry").Init(sfnt,"Val Pal","$SWWM_CVAL2")); diff --git a/zscript/swwm_Polyobjects/PolyobjectEffector.zs b/zscript/swwm_Polyobjects/PolyobjectEffector.zs new file mode 100644 index 000000000..445df752c --- /dev/null +++ b/zscript/swwm_Polyobjects/PolyobjectEffector.zs @@ -0,0 +1,48 @@ +class swwm_PolyobjectEffector: Thinker abstract +{ + // Base abstract class for Polyobject Effectors + // Polyobject Effectors affect how a polyobject behaves. + // Polyobject Effectors contain a pointer to the next Effector of a polyobject, + // forming a circular linked list. + // To add an effector to a polyobject, call AddEffector() on a PolyobjectHandle. + // To remove an effector, simply call Destroy() on it. + + swwm_PolyobjectHandle Polyobject; + swwm_PolyobjectEffector Next; + + // OnAdd() is called once after adding the effector to a PolyobjectHandle + virtual void OnAdd() + { + + } + + // PolyTick() is called every tic by a PolyobjectHandle + virtual void PolyTick() + { + + } + + override void OnDestroy() + { + swwm_PolyobjectEffector e = Polyobject.EffectorList; + if (e != NULL) + { + // Find previous effector + while (e && e.Next != self) + { + e = e.Next; + } + + // Link previous effector to the next effector + e.Next = Next; + + // Check if this effector is the last one + if (e == self) + { + // Polyobject has no other effectors, set EffectorList to NULL + Polyobject.EffectorList = NULL; + } + } + Super.OnDestroy(); + } +} diff --git a/zscript/swwm_Polyobjects/PolyobjectHandle.zs b/zscript/swwm_Polyobjects/PolyobjectHandle.zs new file mode 100644 index 000000000..6898ef040 --- /dev/null +++ b/zscript/swwm_Polyobjects/PolyobjectHandle.zs @@ -0,0 +1,356 @@ +class swwm_PolyobjectHandle: Thinker +{ + // This thinker keeps track of a polyobject's position, angle, movement speed etc. + // Instances of this thinker should only be created by the included map postprocessor + + enum EPolyobjType + { + POTYP_NORMAL = 9301, // Normal StartSpot + POTYP_CRUSH = 9302, // Crush StartSpot + POTYP_HURT = 9303 // Hurt StartSpot + } + + // Polyobject Number + int PolyobjectNum; + + // Line defining the polyobject (Polyobj_StartLine, or one of Polyobj_ExplicitLine) + Line StartLine; + + // Initial angle of StartLine + double StartAngle; + + // Last tic angle of StartLine + double LastAngle; + + // Starting positions of StartLine vertices + Vector2[2] VertexStartingPos; + + // Last tic positions of StartLine vertices + Vector2[2] VertexLastPos; + + // Last tic position of the polyobject + Vector2 LastPos; + + // SoundSequence number + int SoundSequenceNum; + + // StartSpot position + Vector2 StartSpotPos; + + // For bounds checking + double z; + + // Sector the polyobject spawns in () + Sector StartSector; + + // Polyobject type (normal, crush, hurt) + EPolyobjType Type; + + // Mirror Polyobject + swwm_PolyobjectHandle Mirror; + + // Circular linked list of attached Polyobject Effectors + swwm_PolyobjectEffector EffectorList; + + // Whether the initalization has finished + bool IsInitialized; + + // Creates a PolyobjectHandle + static swwm_PolyobjectHandle Create() + { + swwm_PolyobjectHandle po = swwm_PolyobjectHandle(new('swwm_PolyobjectHandle')); + // Sets the underlying Thinker StatNum to 127 so that LastPos, LastAngle etc. get + // updated after all the other thinkers. + po.ChangeStatNum(127); + + return po; + } + + // Returns a PolyobjectHandle corresponding to the provided polyobject number + // Returns NULL if no such handler exists. + static swwm_PolyobjectHandle FindPolyobj(int pobjnum) + { + swwm_PolyobjectHandle po; + let it = ThinkerIterator.Create('swwm_PolyobjectHandle'); + while ((po = swwm_PolyobjectHandle(it.Next())) != NULL) + { + if (po.PolyobjectNum == pobjnum) + return po; + } + return NULL; + } + + // Adds effector to the end of current effector list + void AddEffector(swwm_PolyobjectEffector effector) + { + + effector.Polyobject = self; + + // If effector list is empty, new effector becomes head of the list + if (EffectorList == NULL) + { + EffectorList = effector; + effector.Next = effector; + } + else + { + swwm_PolyobjectEffector e = EffectorList; + // Go through every effector until the last item + while (e.Next != EffectorList) + { + e = e.Next; + } + effector.Next = EffectorList; + e.Next = effector; + } + if (IsInitialized) + { + // If we're initialized, run the OnAdd effect immediately + effector.OnAdd(); + } + } + + // Finds effector of specified class, and returns it + swwm_PolyobjectEffector FindEffector(class effectorclass) + { + // No effectors? Nothing to find then + if (EffectorList == NULL) + return NULL; + + // Go through each effector + swwm_PolyobjectEffector e = EffectorList; + do + { + + // Effector is of specified class, return it + if (e is effectorclass) + return e; + + e = e.Next; + } + while (e != EffectorList); + return NULL; + } + + override void PostBeginPlay() + { + // Initialization shouldn't happen when reentering the map + if (Level.Time > 0) + return; + + // Map has no lines corresponding to this polyobject, destroy the handle + if (StartLine == NULL) + { + Destroy(); + return; + } + + // Using polyobject linedefs is the only way to track polyobject movements. + // All geometric calculations will be done relative to StartLine. + + // Store initial position and angle + StartAngle = VectorAngle(StartLine.Delta.x, StartLine.Delta.y); + VertexStartingPos[0] = VertexLastPos[0] = StartLine.v1.p; + VertexStartingPos[1] = VertexLastPos[1] = StartLine.v2.p; + + // Now that the map is loaded, it's safe to call effectors' OnAdd() methods + swwm_PolyobjectEffector e = EffectorList; + if (e != NULL) + { + do + { + e.OnAdd(); + e = e.Next; + } + while (e != EffectorList); + } + + // Done initializing + IsInitialized = true; + } + + override void Tick() + { + // Call PolyTick() for each effector + if (EffectorList != NULL) + { + swwm_PolyobjectEffector e = EffectorList; + do + { + e.PolyTick(); + e = e.Next; + } + while (e != EffectorList); + } + + // Store current position/angle to be used during the next tic + VertexLastPos[0] = StartLine.v1.p; + VertexLastPos[1] = StartLine.v2.p; + LastAngle = GetAngle(); + LastPos = GetPos(); + } + + override void OnDestroy() + { + // Clean up effectors first + swwm_PolyobjectEffector e = EffectorList; + if (e != NULL) + { + swwm_PolyobjectEffector next; + do + { + next = e.Next; + e.Destroy(); + e = next; + } + while (e != EffectorList); + } + } + + Sector GetSector() + { + if (StartSector == NULL) + { + Vector2 SpotPos = StartSpotPos; + + // Sometimes if StartSpot lies on a one-sided linedef, its position is considered + // out of bounds by GZDoom, which makes Level.PointInSector() produce unexpected + // results. In that case, we need to compensate. + if (!Level.IsPointInLevel((SpotPos, z))) + { + // Look at points in a 5x5 square around the StartSpot + for (int x = -2; x <= 2; x++) + { + for (int y = -2; y <= 2; y++) + { + SpotPos = StartSpotPos + (x, y); + + if (Level.IsPointInLevel((SpotPos, z))) + { + // Found a point within bounds, should be good enough + StartSector = Level.PointInSector(SpotPos); + break; + } + } + if (StartSector) + break; + } + } + else + { + StartSector = Level.PointInSector(SpotPos); + } + } + return StartSector; + } + + // Returns initial StartSpot position + Vector2 GetOrigin() + { + return StartSpotPos; + } + + // Returns current polyobject angle + double GetAngle() + { + double lineangle = VectorAngle(StartLine.Delta.x, StartLine.Delta.y); + return Actor.DeltaAngle(StartAngle, lineangle); + } + + // Returns current polyobject startspot position + Vector2 GetPos() + { + let spotdelta = StartSpotPos - VertexStartingPos[0]; + return StartLine.v1.p + Actor.RotateVector(spotdelta, GetAngle()); + } + + // Returns polyobject coordinates relative to the startspot + Vector2 GetPosDelta() + { + return GetPos() - StartSpotPos; + } + + // Returns last polyobject angle + double GetLastAngle() + { + return LastAngle; + } + + // Returns last polyobject startspot position + Vector2 GetLastPos() + { + return LastPos; + } + + // Returns last coordinates relative to the startspot + Vector2 GetLastPosDelta() + { + return LastPos - StartSpotPos; + } + + // Returns current polyobject velocity + Vector2 GetVel() + { + return StartLine.v1.p - VertexLastPos[0]; + } + + // Returns current polyobject rotation speed + double GetRotationSpeed() + { + return GetAngle() - LastAngle; + } + + // Returns whether the polyobject has moved from its spawn position + bool IsAtOrigin() + { + return (VertexStartingPos[0] == StartLine.v1.p && VertexStartingPos[1] == StartLine.v2.p); + } + + // Returns whether the polyobject is in motion + bool IsMoving() + { + return (GetPos() != GetLastPos() || GetAngle() != GetLastAngle()); + } + + // Moves the polyobject to specified location, with specified speed, and plays the + // specified sound of its sound sequence + // (i.e. for a door sound sequence, sndseqmode 0 plays the open sound, 1 plays the + // closing sound) + void MoveTo(Actor activator, Vector2 dest, int Speed, int sndseqmode = 0) + { + // Stop any polyobject movement + Level.ExecuteSpecial(Polyobj_Stop, activator, StartLine, Line.Front, PolyobjectNum); + // Move the polyobject + Level.ExecuteSpecial(Polyobj_OR_MoveTo, activator, StartLine, Line.Front, PolyobjectNum, + Speed, dest.x, dest.y); + + // Polyobj_OR_MoveTo ignores the sound sequence set by the polyobject. + // Play the sound sequence manually inside the sector containing the polyobject. + if (SoundSequenceNum) + { + GetSector().StartSoundSequenceID(CHAN_AUTO, SoundSequenceNum, SeqNode.DOOR, sndseqmode, false); + } + } +} + +// Class for iterating over polyobjects +class swwm_PolyobjectIterator: Object +{ + private ThinkerIterator it; + static swwm_PolyobjectIterator Create() + { + let it = New('swwm_PolyobjectIterator'); + it.it = ThinkerIterator.Create('swwm_PolyobjectHandle'); + return it; + } + + swwm_PolyobjectHandle Next() + { + return swwm_PolyobjectHandle(it.Next()); + } + + void Reinit() + { + it.Reinit(); + } +} diff --git a/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs b/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs new file mode 100644 index 000000000..4259f8e76 --- /dev/null +++ b/zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs @@ -0,0 +1,84 @@ +// Creates a PolyobjectHandle for every polyobject in the map +class swwm_PolyobjectHandlePostProcessor: LevelPostProcessor +{ + protected void Apply(Name checksum, String mapname) + { + Array pobjnums; + Array pobjhandles; + + // Make sure initialization doesn't happen when reentering a map + if (Level.Time > 0) + return; + + // Look for Polyobject StartSpots and create a handle for each + for (int i = 0; i < GetThingCount(); i++) + { + // Ignore every thing that isn't a Polyobject StartSpot + int ednum = GetThingEdNum(i); + if (ednum < swwm_PolyobjectHandle.POTYP_NORMAL || ednum > swwm_PolyobjectHandle.POTYP_HURT) + continue; + + // Create a PolyobjectHandle + swwm_PolyobjectHandle handle = swwm_PolyobjectHandle.Create(); + + // Get polyobject number from StartSpot angle + handle.PolyobjectNum = GetThingAngle(i); + + // Store StartSpot position + Vector3 pos = GetThingPos(i); + handle.StartSpotPos = pos.xy; + handle.z = pos.z; + + // Store StartSpot type (normal, crush, hurt) + handle.Type = ednum; + + // Append polyobject number and corresponding handle to the respective arrays + pobjnums.Push(handle.PolyobjectNum); + pobjhandles.Push(handle); + } + + // Look for Polyobj_StartLine/Polyobj_ExplicitLine lines + for (int i = 0; i < Level.Lines.Size(); i++) + { + Line line = Level.Lines[i]; + + // Ignore every line that doesn't have a Polyobj_StartLine or Polyobj_ExplicitLine + // line special + if (line.Special != Polyobj_StartLine && line.Special != Polyobj_ExplicitLine) + continue; + + // Get polyobject number + // (Args[0] for both Polyobj_StartLine and Polyobj_ExplicitLine) + int pobjnum = line.Args[0]; + + // Find the array index of the corresponding handle + int pobjhandleindex = pobjnums.Find(pobjnum); + if (pobjhandleindex >= pobjnums.Size()) + continue; // Polyobject doesn't have a corresponding StartSpot + + swwm_PolyobjectHandle handle = pobjhandles[pobjhandleindex]; + + // Get mirror polyobject number + // (Args[1] for Polyobj_StartLine, Args[2] for Polyobj_ExplicitLine) + int mirrorpobjnum = line.Special == Polyobj_StartLine ? line.Args[1] : line.Args[2]; + if (mirrorpobjnum != 0) + { + // Find the array index of the mirror polyobject handle + int mirrorpobjhandleindex = pobjnums.Find(mirrorpobjnum); + if (mirrorpobjhandleindex < pobjnums.Size()) + { + // Mirror polyobject handle exists, store it + handle.Mirror = pobjhandles[mirrorpobjhandleindex]; + } + } + + // Get sound sequence number and store it + // (Args[2] for Polyobj_StartLine, Args[3] for Polyobj_ExplicitLine) + int soundseq = line.Special == Polyobj_StartLine ? line.Args[2] : line.Args[3]; + handle.SoundSequenceNum = soundseq; + + // Store the line + handle.StartLine = line; + } + } +} diff --git a/zscript/swwm_Polyobjects/Polyobjects.zs b/zscript/swwm_Polyobjects/Polyobjects.zs new file mode 100644 index 000000000..56460e605 --- /dev/null +++ b/zscript/swwm_Polyobjects/Polyobjects.zs @@ -0,0 +1,3 @@ +#include "zscript/swwm_Polyobjects/PolyobjectHandle.zs" +#include "zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs" +#include "zscript/swwm_Polyobjects/PolyobjectEffector.zs" diff --git a/zscript/swwm_thinkers.zsc b/zscript/swwm_thinkers.zsc index 447f8fec6..d217566a0 100644 --- a/zscript/swwm_thinkers.zsc +++ b/zscript/swwm_thinkers.zsc @@ -152,15 +152,15 @@ Class SWWMDamageAccumulator : Thinker } // prevents floors/ceilings from ever moving again, as they're "broken crushers" -// optional "instant" parameter is used by wall busting, no crusher malfunction animation will play Class SWWMCrusherBroken : Thinker { Sector fsec, csec; double diffh; int fphase, cphase; int ftics, ctics; + SectorEffect fse, cse; // pointers to zero-speed movers - static void Create( Sector f, Sector c, double diffh, bool instant = false ) + static void Create( Sector f, Sector c, double diffh ) { if ( !f && !c ) return; let ti = ThinkerIterator.Create("SWWMCrusherBroken",STAT_USER); @@ -168,15 +168,7 @@ Class SWWMCrusherBroken : Thinker while ( cb = SWWMCrusherBroken(ti.Next()) ) { if ( (cb.fsec == f) && (cb.csec == c) ) - { - if ( instant ) - { - // force it to be instant - if ( f ) cb.fphase = 3; - if ( c ) cb.cphase = 3; - } return; // we already have this - } if ( cb.fsec && (cb.fsec == f) ) { cb.Destroy(); // we override this one @@ -195,10 +187,21 @@ Class SWWMCrusherBroken : Thinker cb.diffh = diffh; if ( f && f.floordata ) f.floordata.Destroy(); if ( c && c.ceilingdata ) c.ceilingdata.Destroy(); - if ( instant ) + } + + static void Remove( Sector f, Sector c ) + { + if ( !f && !c ) return; + let ti = ThinkerIterator.Create("SWWMCrusherBroken",STAT_USER); + SWWMCrusherBroken cb; + while ( cb = SWWMCrusherBroken(ti.Next()) ) { - if ( f ) cb.fphase = 3; - if ( c ) cb.cphase = 3; + if ( (cb.fsec == f) && (cb.csec == c) ) + cb.Destroy(); // destroy entirely + else if ( f && (cb.fsec == f) ) + cb.fsec = null; // only clear the floor + else if ( c && (cb.csec == c) ) + cb.csec = null; // only clear the ceiling } } @@ -228,10 +231,12 @@ Class SWWMCrusherBroken : Thinker ftics--; if ( ftics <= 0 ) fphase = 3; } - else if ( (fphase >= 3) && fsec.floordata ) + else if ( (fphase >= 3) && (!fse || (fsec.floordata != fse)) ) { - fsec.floordata.Destroy(); + if ( fsec.floordata ) fsec.floordata.Destroy(); + level.CreateFloor(fsec,Floor.floorLowerByValue,null,0.,1.); fsec.StopSoundSequence(CHAN_WEAPON); + fse = fsec.floordata; } } if ( csec ) @@ -258,10 +263,12 @@ Class SWWMCrusherBroken : Thinker ctics--; if ( ctics <= 0 ) cphase = 3; } - else if ( (cphase >= 3) && csec.ceilingdata ) + else if ( (cphase >= 3) && (!cse || (csec.ceilingdata != cse)) ) { - csec.ceilingdata.Destroy(); + if ( csec.ceilingdata ) csec.ceilingdata.Destroy(); + level.CreateCeiling(csec,Ceiling.ceilRaiseByValue,null,0.,0.,1.); csec.StopSoundSequence(CHAN_VOICE); + cse = csec.ceilingdata; } } } diff --git a/zscript/utility/swwm_utility.zsc b/zscript/utility/swwm_utility.zsc index 917562fc9..c4ffdf0fb 100644 --- a/zscript/utility/swwm_utility.zsc +++ b/zscript/utility/swwm_utility.zsc @@ -1741,6 +1741,12 @@ Class SWWMUtility return false, checkme; } + static bool IsPolyLine( Line l, out swwm_PolyobjectHandle p ) + { + // TODO iterate through polyobjects and see if this line is connected to their start line + return false; + } + // full reset of inventory (excluding collectibles, and optionally resetting the score) static play void WipeInventory( Actor mo, bool resetscore = false, bool allplayers = false ) { diff --git a/zscript/weapons/swwm_cbt_fx.zsc b/zscript/weapons/swwm_cbt_fx.zsc index e85fa44c0..7e07a0ab9 100644 --- a/zscript/weapons/swwm_cbt_fx.zsc +++ b/zscript/weapons/swwm_cbt_fx.zsc @@ -227,6 +227,11 @@ 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 ) + { + return false; + } + static bool Bust( TraceResults d, int accdamage, Actor instigator, Vector3 x, double hitz ) { // we can't blow up 3D floors @@ -235,6 +240,9 @@ Class BusterWall : Thinker 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,hitz); // no busting the goat if ( IsIOSWall(d.HitLine) ) return false; // onesided wall? no bust @@ -286,6 +294,8 @@ Class BusterWall : Thinker 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); @@ -353,6 +363,9 @@ Class BusterWall : Thinker 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)); @@ -374,39 +387,37 @@ Class BusterWall : Thinker if ( !l.sidedef[1] ) continue; int away = 0; if ( l.sidedef[0].sector == hs ) away = 1; - // temporarily set filler texture so switches don't play a sound - TextureID oldtex[3][2]; - for ( int j=0; j<3; j++ ) for ( int k=0; k<2; k++ ) - { - oldtex[j][k] = l.sidedef[k].GetTexture(j); - l.sidedef[k].SetTexture(j,rubble); - } l.Activate(instigator,away,SPAC_Use); l.Activate(instigator,away,SPAC_Impact); - for ( int j=0; j<3; j++ ) for ( int k=0; k<2; k++ ) - l.sidedef[k].SetTexture(j,oldtex[j][k]); - // clear any use/impact specials - if ( l.Activation&(SPAC_Use|SPAC_Impact) ) - l.Activation &= ~(SPAC_Use|SPAC_Impact); } - // stop movement permanently - SWWMCrusherBroken.Create(hp?null:hs,hp?hs:null,0.,true); + // 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);