More changes from master.
This commit is contained in:
parent
e0897b53e4
commit
5dea5ab4d6
21 changed files with 892 additions and 42 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ nosave bool swwm_nomapmsg = false; // disables special map dialogue messages
|
|||
nosave bool swwm_weapontooltips = true; // shows a tooltip on weapon operation when first selected
|
||||
nosave string swwm_tooltipshown = ""; // which weapons have already had their tooltips displayed
|
||||
nosave noarchive bool swwm_tooltipnote = false; // set if the note on how to disable tooltips was already displayed
|
||||
nosave bool swwm_nointertips = false; // hides intermission tips, in case you don't want to see them
|
||||
|
||||
// minimap settings
|
||||
nosave bool swwm_mm_enable = true; // show a minimap below the score counter
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ SWWM_WEAPONTOOLTIPS = "Weapon Tooltips";
|
|||
SWWM_RESETTOOLTIPS = "Reset Weapon Tooltips";
|
||||
SWWM_CRESET = "Reset to Defaults";
|
||||
SWWM_ARESET = "Wipe Achievements";
|
||||
SWWM_NOINTERTIPS = "Hide Intermission Tips";
|
||||
SWWM_MMTITLE = "Minimap Settings";
|
||||
SWWM_MM_ENABLE = "Show Minimap";
|
||||
SWWM_MM_ROTATE = "Rotate Minimap";
|
||||
|
|
@ -359,6 +360,7 @@ TOOLTIP_SWWM_WEAPONTOOLTIPS = "Shows a summary of controls when a weapon is sele
|
|||
TOOLTIP_EVENT_SWWMRESETTOOLTIPS = "Resets weapon tooltips so you can see them all over again.";
|
||||
TOOLTIP_EVENT_SWWMRESETCVARS = "Resets all the mod's settings to their original values.";
|
||||
TOOLTIP_EVENT_SWWMRESETACHIEVEMENTS = "Erases all your archievement progress.";
|
||||
TOOLTIP_SWWM_NOINTERTIPS = "Hides intermission tips, in case you don't want to see them.";
|
||||
TOOLTIP_SWWMMINIMAPMENU = "Configure the minimap.";
|
||||
TOOLTIP_SWWMACHIEVEMENTMENU = "View your achievements.";
|
||||
TOOLTIP_SWWM_MM_ENABLE = "Displays a minimap under the score counter.";
|
||||
|
|
@ -414,6 +416,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.";
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ SWWM_WEAPONTOOLTIPS = "Ayuda de Armas";
|
|||
SWWM_RESETTOOLTIPS = "Resetear Ayuda de Armas";
|
||||
SWWM_CRESET = "Restaurar Predeterminado";
|
||||
SWWM_ARESET = "Borrar Logros";
|
||||
SWWM_NOINTERTIPS = "Ocultar Consejos de Intermisión";
|
||||
SWWM_MMTITLE = "Opciones de Minimapa";
|
||||
SWWM_MM_ENABLE = "Mostrar Minimapa";
|
||||
SWWM_MM_ROTATE = "Rotar Minimapa";
|
||||
|
|
@ -358,6 +359,7 @@ TOOLTIP_SWWM_WEAPONTOOLTIPS = "Muestra un resumen de controles cuando se selecci
|
|||
TOOLTIP_EVENT_SWWMRESETTOOLTIPS = "Resetea la ayuda de armas para que puedas volver a verla de nuevo.";
|
||||
TOOLTIP_EVENT_SWWMRESETCVARS = "Resetea todas las opciones del mod a sus valores originales.";
|
||||
TOOLTIP_EVENT_SWWMRESETACHIEVEMENTS = "Borra todo tu progreso de logros.";
|
||||
TOOLTIP_SWWM_NOINTERTIPS = "Oculta los consejos de intermisión, por si no quieres verlos.";
|
||||
TOOLTIP_SWWMMINIMAPMENU = "Configura el minimapa.";
|
||||
TOOLTIP_SWWMACHIEVEMENTMENU = "Revisa tus logros.";
|
||||
TOOLTIP_SWWM_MM_ENABLE = "Muestra un minimapa bajo el contador de puntuación.";
|
||||
|
|
@ -411,6 +413,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.";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
[default]
|
||||
SWWM_MODVER="\chSWWM \czGZ\c- \cw1.2pre r4 \cu(Mon 27 Sep 12:31:19 CEST 2021)\c-";
|
||||
SWWM_SHORTVER="\cw1.2pre r4 \cu(2021-09-27 12:31:19)\c-";
|
||||
SWWM_MODVER="\chSWWM \czGZ\c- \cw1.2pre r5 \cu(Wed 29 Sep 18:47:23 CEST 2021)\c-";
|
||||
SWWM_SHORTVER="\cw1.2pre r5 \cu(2021-09-29 18:47:23)\c-";
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ OptionMenu "SWWMOptionMenu"
|
|||
Option "$SWWM_NUMCOLOR_HP", "swwm_numcolor_hp", "TextColors"
|
||||
Option "$SWWM_NUMCOLOR_AP", "swwm_numcolor_ap", "TextColors"
|
||||
Option "$SWWM_INTERART", "swwm_interart", "YesNo"
|
||||
Option "$SWWM_NOINTERTIPS", "swwm_nointertips", "YesNo"
|
||||
Option "$SWWM_INTERMUSIC", "swwm_intermusic", "YesNo"
|
||||
Option "$SWWM_FUZZ", "swwm_fuzz", "YesNo"
|
||||
Option "$SWWM_SILENCEMAP", "swwm_silencemap", "YesNo"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -332,7 +332,7 @@ Class SWWMLevelCompatibility : LevelPostProcessor
|
|||
case '959A613006CC3AA912C4A22908B7566A':
|
||||
// add collectibles
|
||||
for ( int i=0; i<12; i++ )
|
||||
AddThing(4206900+i,(1472+64*i,640,0),SKILLS_ALL,MTF_SINGLE|MTF_COOPERATIVE);
|
||||
AddThing(4206900+i,(1472+64*i,640,0),0,SKILLS_ALL,MTF_SINGLE|MTF_COOPERATIVE);
|
||||
// add some chance boxes
|
||||
for ( int i=0; i<3; i++ )
|
||||
AddThing(4206920,(3616,1824-64*i,0),180,SKILLS_ALL,MTF_SINGLE|MTF_COOPERATIVE);
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ Class HammerspaceEmbiggener : Inventory
|
|||
i.MaxAmount = int(i.default.MaxAmount+Amount*factor);
|
||||
}
|
||||
int amount = Ammo(i).BackpackAmount*item.Amount;
|
||||
if ( traded ) i.Amount = 0;
|
||||
if ( traded ) amount = 0;
|
||||
if ( (i.Amount > 0) && (i.Amount+amount < 0) )
|
||||
i.Amount = int.max;
|
||||
else i.Amount += amount;
|
||||
|
|
|
|||
|
|
@ -719,6 +719,9 @@ Class ChanceboxSpawner : Actor
|
|||
if ( deathmatch )
|
||||
{
|
||||
// not in DM
|
||||
let b = Spawn("HealthNuggetItem",pos);
|
||||
SWWMUtility.TransferItemProp(self,b);
|
||||
ClearCounters();
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
|
@ -728,6 +731,9 @@ Class ChanceboxSpawner : Actor
|
|||
if ( numbox >= 3 )
|
||||
{
|
||||
// there's three boxes in the map already
|
||||
let b = Spawn("HealthNuggetItem",pos);
|
||||
SWWMUtility.TransferItemProp(self,b);
|
||||
ClearCounters();
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
|
@ -748,6 +754,9 @@ Class ChanceboxSpawner : Actor
|
|||
if ( tbox[1] > l.bbox[0] ) continue;
|
||||
if ( SWWMUtility.BoxOnLineSide(tbox[0],tbox[1],tbox[2],tbox[3],l) != -1 ) continue;
|
||||
// there isn't enough space to spawn a box here
|
||||
let b = Spawn("HealthNuggetItem",pos);
|
||||
SWWMUtility.TransferItemProp(self,b);
|
||||
ClearCounters();
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ Class SWWMStatScreen : StatusScreen abstract
|
|||
whichtip = ents[Random[InterArt](0,ents.Size()-1)];
|
||||
pdata.lasttip.Push(whichtip);
|
||||
}
|
||||
if ( swwm_nointertips ) return;
|
||||
String tipstr = "\cd"..String.Format(StringTable.Localize("$SWWM_INTERTIP"),whichtip).."\c-\n"..StringTable.Localize(String.Format("$SWWM_INTERTIP%d",whichtip));
|
||||
BrokenLines l = fnt.BreakLines(tipstr,400);
|
||||
int lw = 0;
|
||||
|
|
|
|||
48
zscript/swwm_Polyobjects/PolyobjectEffector.zs
Normal file
48
zscript/swwm_Polyobjects/PolyobjectEffector.zs
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
359
zscript/swwm_Polyobjects/PolyobjectHandle.zs
Normal file
359
zscript/swwm_Polyobjects/PolyobjectHandle.zs
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
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;
|
||||
|
||||
// [MK] All lines belonging to the polyobject
|
||||
Array<Line> Lines;
|
||||
|
||||
// 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<swwm_PolyobjectEffector> 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();
|
||||
}
|
||||
}
|
||||
123
zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs
Normal file
123
zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// Creates a PolyobjectHandle for every polyobject in the map
|
||||
class swwm_PolyobjectHandlePostProcessor: LevelPostProcessor
|
||||
{
|
||||
protected void Apply(Name checksum, String mapname)
|
||||
{
|
||||
Array<int> pobjnums;
|
||||
Array<swwm_PolyobjectHandle> 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);
|
||||
// [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;
|
||||
|
||||
// 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;
|
||||
|
||||
// [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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
zscript/swwm_Polyobjects/Polyobjects.zs
Normal file
3
zscript/swwm_Polyobjects/Polyobjects.zs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include "zscript/swwm_Polyobjects/PolyobjectHandle.zs"
|
||||
#include "zscript/swwm_Polyobjects/PolyobjectMapPostprocessor.zs"
|
||||
#include "zscript/swwm_Polyobjects/PolyobjectEffector.zs"
|
||||
|
|
@ -1579,6 +1579,47 @@ Class Demolitionist : PlayerPawn
|
|||
}
|
||||
SWWMUtility.MarkAchievement("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' )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -151,16 +151,32 @@ 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"
|
||||
// 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 +184,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 +203,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 +247,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 +279,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1670,6 +1670,62 @@ Class SWWMUtility
|
|||
return false, checkme;
|
||||
}
|
||||
|
||||
// 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 )
|
||||
{
|
||||
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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,6 +275,124 @@ Class BusterWall : Thinker
|
|||
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('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 )
|
||||
{
|
||||
// we can't blow up 3D floors
|
||||
|
|
@ -235,6 +401,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);
|
||||
// no busting the goat
|
||||
if ( IsIOSWall(d.HitLine) ) return false;
|
||||
// onesided wall? no bust
|
||||
|
|
@ -286,6 +455,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 +524,9 @@ Class BusterWall : Thinker
|
|||
if ( s ) s.busts++;
|
||||
SWWMUtility.AchievementProgressInc("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 +548,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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue