Split player and utility code.
This commit is contained in:
parent
1fe50b5c32
commit
44f39d1024
26 changed files with 6908 additions and 6619 deletions
|
|
@ -1,3 +1,3 @@
|
|||
[default]
|
||||
SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r693 \cu(Thu 15 Dec 14:31:43 CET 2022)\c-";
|
||||
SWWM_SHORTVER="\cw1.3pre r693 \cu(2022-12-15 14:31:43)\c-";
|
||||
SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r694 \cu(Thu 15 Dec 17:12:53 CET 2022)\c-";
|
||||
SWWM_SHORTVER="\cw1.3pre r694 \cu(2022-12-15 17:12:53)\c-";
|
||||
|
|
|
|||
26
zscript.txt
26
zscript.txt
|
|
@ -11,20 +11,38 @@ version "4.10"
|
|||
#include "zscript/swwm_Gutamatics/Include.zsc"
|
||||
// ZPolyobject
|
||||
#include "zscript/swwm_Polyobjects/Polyobjects.zs"
|
||||
// utility code
|
||||
#include "zscript/utility/swwm_utility.zsc"
|
||||
#include "zscript/utility/swwm_utility_achievements.zsc"
|
||||
#include "zscript/utility/swwm_utility_blast.zsc"
|
||||
#include "zscript/utility/swwm_utility_compat.zsc"
|
||||
#include "zscript/utility/swwm_utility_info.zsc"
|
||||
#include "zscript/utility/swwm_utility_item.zsc"
|
||||
#include "zscript/utility/swwm_utility_map.zsc"
|
||||
#include "zscript/utility/swwm_utility_math.zsc"
|
||||
#include "zscript/utility/swwm_utility_string.zsc"
|
||||
// base code
|
||||
#include "zscript/swwm_utility.zsc"
|
||||
#include "zscript/swwm_common.zsc"
|
||||
#include "zscript/swwm_common_fx.zsc"
|
||||
#include "zscript/swwm_handler.zsc"
|
||||
#include "zscript/swwm_statichandler.zsc"
|
||||
#include "zscript/swwm_thinkers.zsc"
|
||||
#include "zscript/swwm_thinkers_player.zsc"
|
||||
#include "zscript/swwm_player.zsc"
|
||||
#include "zscript/swwm_player_fx.zsc"
|
||||
#include "zscript/swwm_player_items.zsc"
|
||||
#include "zscript/swwm_gesture.zsc"
|
||||
#include "zscript/swwm_gesture_fx.zsc"
|
||||
#include "zscript/swwm_blod.zsc"
|
||||
// player code
|
||||
#include "zscript/player/swwm_player.zsc"
|
||||
#include "zscript/player/swwm_player_anim.zsc"
|
||||
#include "zscript/player/swwm_player_cheats.zsc"
|
||||
#include "zscript/player/swwm_player_extra.zsc"
|
||||
#include "zscript/player/swwm_player_face.zsc"
|
||||
#include "zscript/player/swwm_player_fx.zsc"
|
||||
#include "zscript/player/swwm_player_inventory.zsc"
|
||||
#include "zscript/player/swwm_player_items.zsc"
|
||||
#include "zscript/player/swwm_player_move.zsc"
|
||||
#include "zscript/player/swwm_player_think.zsc"
|
||||
#include "zscript/player/swwm_player_tick.zsc"
|
||||
// handler code
|
||||
#include "zscript/handler/swwm_handler_cheats.zsc"
|
||||
#include "zscript/handler/swwm_handler_crosshair.zsc"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ extend Class SWWMHandler
|
|||
e.Thing.vel *= 2;
|
||||
Vector3 x, y, z;
|
||||
double ang = e.Thing.target.target?e.Thing.AngleTo(e.Thing.target.target):e.Thing.angle;
|
||||
double pt = e.Thing.target.target?SWWMUtility.PitchTo(e.Thing,e.Thing.target.target,.5):e.Thing.pitch;
|
||||
double pt = e.Thing.target.target?e.Thing.PitchTo(e.Thing.target.target,e.Thing.target.missileheight,e.Thing.target.target.Height/2.):e.Thing.pitch;
|
||||
[x, y, z] = SWWMUtility.GetAxes(ang,pt,e.Thing.roll);
|
||||
int numpt = Random[ExtraMissiles](1,2);
|
||||
for ( int i=0; i<numpt; i++ )
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ Class MykradvoBurst : SWWMNonInteractiveActor
|
|||
{
|
||||
let t = Spawn("MykradvoTendril",pos);
|
||||
t.angle = t.AngleTo(targ);
|
||||
t.pitch = SWWMUtility.PitchTo(t,targ,.5);
|
||||
t.pitch = t.PitchTo(targ,0,targ.Height/2);
|
||||
t.target = target;
|
||||
t.tracer = targ;
|
||||
}
|
||||
|
|
|
|||
763
zscript/player/swwm_player.zsc
Normal file
763
zscript/player/swwm_player.zsc
Normal file
|
|
@ -0,0 +1,763 @@
|
|||
// The Demolitionist
|
||||
|
||||
Enum EDemoFaceState
|
||||
{
|
||||
FS_DEFAULT,
|
||||
FS_EVIL,
|
||||
FS_GRIN,
|
||||
FS_WINK,
|
||||
FS_BLINK,
|
||||
FS_SAD,
|
||||
FS_PAIN,
|
||||
FS_OUCH,
|
||||
FS_DEAD // UNUSED
|
||||
};
|
||||
|
||||
Class Demolitionist : PlayerPawn
|
||||
{
|
||||
int last_jump_held, last_boost, last_kick;
|
||||
Vector3 dashdir;
|
||||
double dashfuel, dashboost;
|
||||
int dashcooldown, boostcooldown, fuelcooldown;
|
||||
int dashlockst;
|
||||
bool fullfuel;
|
||||
bool sendtoground;
|
||||
bool key_reentrant;
|
||||
bool bInDefaultInventory;
|
||||
bool oldsinglefirst;
|
||||
|
||||
transient int lastdamage;
|
||||
transient int lastdamagetic, lastdamagetimer;
|
||||
bool lastground;
|
||||
int lastgroundtic, lastairtic;
|
||||
double lastvelz, prevvelz, landvelz;
|
||||
double ssup;
|
||||
|
||||
transient CVar myvoice;
|
||||
|
||||
SWWMStats mystats;
|
||||
int cairtime;
|
||||
bool hasteleported;
|
||||
bool hasrevived;
|
||||
|
||||
int lastmpain;
|
||||
|
||||
double guideangle, guidepitch, guideroll;
|
||||
|
||||
// for weapon bobbing stuff
|
||||
double bumpvelz, bumpangle, bumppitch, bumproll;
|
||||
double oldangle, oldpitch;
|
||||
double oldlagangle, oldlagpitch, oldlagready;
|
||||
Vector3 oldlagvel;
|
||||
double lagangle, lagpitch, lagready;
|
||||
Vector3 lagvel;
|
||||
|
||||
enum EUnderType
|
||||
{
|
||||
UNDER_NONE,
|
||||
UNDER_WATER,
|
||||
UNDER_SLIME,
|
||||
UNDER_LAVA
|
||||
};
|
||||
|
||||
int lastunder;
|
||||
Color undercol;
|
||||
int deadtimer;
|
||||
transient int revivefail;
|
||||
|
||||
transient int bumptic;
|
||||
transient double lastbump;
|
||||
transient CVar bumpstr;
|
||||
|
||||
DemolitionistSelfLight selflight;
|
||||
Actor oldencroached;
|
||||
Vector3 oldencroachedpos;
|
||||
int encroachtics;
|
||||
|
||||
Vector3 pretelepos;
|
||||
|
||||
SWWMItemSense itemsense;
|
||||
|
||||
int healcooldown, healtimer, oldhealth;
|
||||
bool scriptedinvul;
|
||||
bool hitactivate;
|
||||
Actor froggy;
|
||||
|
||||
transient int lastuse, failcounter, failcooldown;
|
||||
transient SWWMItemTracer itrace;
|
||||
bool meleeuse;
|
||||
|
||||
transient bool bWalking;
|
||||
|
||||
int airscreamtime;
|
||||
|
||||
enum EInvWipe
|
||||
{
|
||||
WIPE_EPISODE = 1,
|
||||
WIPE_CLUSTER = 2,
|
||||
WIPE_MAP = 4
|
||||
}
|
||||
|
||||
int invwipe; // inventory wipe flags for next level
|
||||
|
||||
transient int lastbang, lastbust;
|
||||
|
||||
transient bool ingivecheat;
|
||||
|
||||
Property DashFuel : dashfuel;
|
||||
|
||||
EDemoFaceState facestate;
|
||||
int paindir;
|
||||
int facetimer;
|
||||
int blinktime;
|
||||
transient int oldfaceidx;
|
||||
transient int rss;
|
||||
transient bool facedamage, facegrin, facesad, facewink, faceblink;
|
||||
|
||||
transient CVar tagcolor;
|
||||
int oldtagcolor;
|
||||
|
||||
transient int magtime;
|
||||
SWWMMagItem magitem;
|
||||
int magitem_cnt;
|
||||
|
||||
SWWMShadow myshadow;
|
||||
|
||||
double bobtime, oldbobtime, oldbob;
|
||||
|
||||
Default
|
||||
{
|
||||
Tag "$T_DEMOLITIONIST";
|
||||
Speed 1;
|
||||
Radius 16; // should be 9 in theory, but it'd be too thin
|
||||
Height 56;
|
||||
Mass 500;
|
||||
PainChance 255;
|
||||
MaxSlopeSteepness 0; // mountain goat mode, all slopes are walkable
|
||||
Player.DisplayName "$T_DEMOLITIONIST";
|
||||
// StartItem array is defined but not used directly
|
||||
// just declared here for mod compat
|
||||
Player.StartItem "ExplodiumGun";
|
||||
Player.StartItem "DeepImpact";
|
||||
Player.StartItem "AlmasteelPlating";
|
||||
Player.StartItem "SayaCollar";
|
||||
Player.ViewHeight 52;
|
||||
Player.AirCapacity 0;
|
||||
Player.GruntSpeed 20;
|
||||
Player.ForwardMove 1., 1.;
|
||||
Player.SideMove 1., 1.;
|
||||
Player.SoundClass "demolitionist";
|
||||
DamageFactor "Drowning", 0.;
|
||||
DamageFactor "Poison", 0.;
|
||||
DamageFactor "PoisonCloud", 0.;
|
||||
DamageFactor "Falling", 0.;
|
||||
Demolitionist.DashFuel 240.;
|
||||
+NOBLOOD;
|
||||
+DONTGIB;
|
||||
+NOICEDEATH;
|
||||
+NOSKIN;
|
||||
+DONTMORPH;
|
||||
+DONTDRAIN;
|
||||
+DONTCORPSE;
|
||||
-WINDTHRUST; // too heavy
|
||||
}
|
||||
|
||||
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
||||
{
|
||||
if ( inflictor && inflictor.FindInventory("ParriedBuff") ) return StringTable.Localize("$O_PARRY");
|
||||
if ( mod == 'Jump' ) return StringTable.Localize("$O_JUMP");
|
||||
if ( mod == 'Dash' ) return StringTable.Localize("$O_DASH");
|
||||
if ( mod == 'Buttslam' ) return StringTable.Localize("$O_BUTT");
|
||||
if ( mod == 'GroundPound' ) return StringTable.Localize("$O_POUND");
|
||||
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
||||
}
|
||||
|
||||
override double GetDeathHeight()
|
||||
{
|
||||
double basedeathheight = Super.GetDeathHeight();
|
||||
// limit death height to crouch height, to avoid dying players from getting stuck on revive
|
||||
return max(Height*.3,basedeathheight);
|
||||
}
|
||||
|
||||
override void PostBeginPlay()
|
||||
{
|
||||
Super.PostBeginPlay();
|
||||
lastground = true; // prevent sudden landing sound on map start
|
||||
blinktime = 30;
|
||||
// swap ourselves for a voodoo doll
|
||||
if ( !player || (player.mo != self) )
|
||||
{
|
||||
let v = Spawn("SWWMVoodooDoll",pos);
|
||||
v.angle = angle;
|
||||
v.player = player;
|
||||
// give it a face if it belongs to a player
|
||||
if ( player )
|
||||
{
|
||||
v.bFRIENDLY = true;
|
||||
v.A_ChangeModel("",0,"","",1,"models","VoodooDollFace.png",CMDL_USESURFACESKIN,-1);
|
||||
}
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
oldsinglefirst = swwm_singlefirst; // super already sets up the slots, so save the cvar value now
|
||||
mystats = SWWMStats.Find(player);
|
||||
// sanity checks
|
||||
if ( !EventHandler.Find("SWWMHandler") || !StaticEventHandler.Find("SWWMStaticHandler") )
|
||||
ThrowAbortException("Panic! SWWM event handlers not detected!");
|
||||
}
|
||||
|
||||
void BumpView( double str, Vector3 dir = (0,0,0) )
|
||||
{
|
||||
double dirlen = dir.length();
|
||||
if ( dirlen < double.epsilon )
|
||||
{
|
||||
bumppitch += str;
|
||||
return;
|
||||
}
|
||||
dir /= dirlen;
|
||||
Vector3 x, y;
|
||||
Quat r = Quat.FromAngles(angle+viewangle,pitch+viewpitch,roll+viewroll);
|
||||
x = r*(1,0,0);
|
||||
y = r*(0,-1,0);
|
||||
double sx = dir dot x;
|
||||
double sy = dir dot y;
|
||||
if ( !sx && !sy ) bumppitch += str;
|
||||
else
|
||||
{
|
||||
bumppitch += str*sx;
|
||||
bumproll += str*sy;
|
||||
}
|
||||
}
|
||||
|
||||
override void CheckFOV()
|
||||
{
|
||||
if ( !player ) return;
|
||||
float desired = player.desiredfov;
|
||||
// adjust fov from weapon (abs due to special use of negative
|
||||
// to prevent it from scaling look sensitivity)
|
||||
if ( (player.playerstate != PST_DEAD) && player.readyweapon
|
||||
&& player.readyweapon.fovscale )
|
||||
desired *= abs(player.readyweapon.fovscale);
|
||||
// additional fov bump from various effects
|
||||
// akin to the old A_ZoomFactor trick, but not limited to weapons and can stack
|
||||
if ( lastbump <= 0. ) lastbump = 1.;
|
||||
if ( lastbump != 1. )
|
||||
{
|
||||
if ( !bumpstr ) bumpstr = CVar.GetCVar('swwm_bumpstrength',player);
|
||||
double str = bumpstr.GetFloat();
|
||||
player.fov *= lastbump*str+1.-str;
|
||||
lastbump = 1.;
|
||||
}
|
||||
// adjust fov from dashing
|
||||
double spd = vel.length();
|
||||
if ( InStateSequence(CurState,FindState("Dash")) && (spd > 10.) )
|
||||
{
|
||||
Vector3 facedir = SWWMUtility.Vec3FromAngles(angle,pitch);
|
||||
if ( spd > 0. )
|
||||
{
|
||||
double rel = max(0,vel.unit() dot facedir);
|
||||
desired *= 1.+clamp(rel*(spd-10.),-80.,80.)*.002;
|
||||
}
|
||||
}
|
||||
if ( player.fov == desired ) return;
|
||||
// interpolate towards desired fov
|
||||
if ( abs(player.fov-desired) < .1 ) player.fov = desired;
|
||||
else
|
||||
{
|
||||
float zoom = max(.1,abs(player.fov-desired)*.35);
|
||||
if ( player.fov > desired ) player.fov -= zoom;
|
||||
else player.fov += zoom;
|
||||
}
|
||||
}
|
||||
|
||||
override void CheckPoison()
|
||||
{
|
||||
// HAHA no
|
||||
player.poisoncount = 0;
|
||||
}
|
||||
|
||||
private void CheckBreakCrusher()
|
||||
{
|
||||
double gaph = (ceilingz-floorz);
|
||||
if ( gaph > height*.8 ) return;
|
||||
// the smaller the gap, the more likely the crusher will snap
|
||||
if ( Random[Demolitionist](0,2) && (FRandom[Demolitionist](0,gaph/height) > .2) ) return;
|
||||
double diffh = 8.+(default.height-gaph); // how much the crusher will have to "snap" after breaking
|
||||
let ceil = ceilingsector;
|
||||
let flor = floorsector;
|
||||
let ceilse = ceil.ceilingdata;
|
||||
let florse = flor.floordata;
|
||||
if ( ceilse && florse )
|
||||
{
|
||||
// snap both planes
|
||||
let q = Spawn("BustedQuake",(ceil.centerspot.x,ceil.centerspot.y,ceil.ceilingplane.ZAtPoint(ceil.centerspot)));
|
||||
q.special1 = 6;
|
||||
q = Spawn("BustedQuake",(flor.centerspot.x,flor.centerspot.y,flor.floorplane.ZAtPoint(flor.centerspot)));
|
||||
q.special1 = 6;
|
||||
SWWMCrusherBroken.Create(flor,ceil,diffh/2.);
|
||||
}
|
||||
else if ( ceilse )
|
||||
{
|
||||
// snap ceiling
|
||||
let q = Spawn("BustedQuake",(ceil.centerspot.x,ceil.centerspot.y,ceil.ceilingplane.ZAtPoint(ceil.centerspot)));
|
||||
q.special1 = 10;
|
||||
SWWMCrusherBroken.Create(null,ceil,diffh);
|
||||
}
|
||||
else if ( florse )
|
||||
{
|
||||
// snap floor
|
||||
let q = Spawn("BustedQuake",(flor.centerspot.x,flor.centerspot.y,flor.floorplane.ZAtPoint(flor.centerspot)));
|
||||
q.special1 = 10;
|
||||
SWWMCrusherBroken.Create(flor,null,diffh);
|
||||
}
|
||||
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;
|
||||
foreach ( l:touching )
|
||||
{
|
||||
if ( p.Lines.Find(l) >= 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("crush",player);
|
||||
if ( p.Mirror && BusterWall.BustPolyobj(p.Mirror,max(dmg,(100-health)*5),self,-(diragainst.x,diragainst.y,0)) )
|
||||
SWWMUtility.MarkAchievement("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)
|
||||
if ( (mod == 'Falling') | (mod == 'Drowning') || (mod == 'Poison') || (mod == 'PoisonCloud') )
|
||||
damage = 0;
|
||||
if ( (swwm_strictuntouchable >= 2) && (damage > 0) && player )
|
||||
{
|
||||
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
||||
if ( hnd ) hnd.tookdamage[PlayerNumber()] = true;
|
||||
}
|
||||
if ( (mod == 'Crush') && player && (player.mo == self) )
|
||||
{
|
||||
// check if we can break any active crushers
|
||||
// (or polyobjects)
|
||||
if ( !inflictor && !source )
|
||||
{
|
||||
CheckBreakCrusher();
|
||||
CheckBreakPolyobject(damage);
|
||||
}
|
||||
// break a spike trap
|
||||
else if ( source is 'ThrustFloor' )
|
||||
{
|
||||
let q = Spawn("BustedQuake",source.pos);
|
||||
q.special1 = 4;
|
||||
int numpt = Random[ExploS](30,40);
|
||||
for ( int i=0; i<numpt; i++ )
|
||||
{
|
||||
let s = Spawn("SWWMChip",source.Vec3Angle(source.radius,FRandom[ExploS](0,360),1.+FRandom[ExploS](0,max(0.,source.height-source.floorclip))));
|
||||
s.vel = ((0,0,1)+SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*.6).unit()*FRandom[ExploS](2.,16.);
|
||||
s.scale *= FRandom[ExploS](1.5,3.);
|
||||
s.A_SetTranslation('StoneSpike');
|
||||
}
|
||||
numpt = Random[ExploS](12,16);
|
||||
for ( int i=0; i<numpt; i++ )
|
||||
{
|
||||
let s = Spawn("SWWMHalfSmoke",source.Vec3Offset(0,0,1.+FRandom[ExploS](0,max(0.,source.height-source.floorclip))));
|
||||
s.vel = ((0,0,1)+SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))).unit()*FRandom[ExploS](-2,8.);
|
||||
s.scale *= 2.5;
|
||||
s.special1 = Random[ExploS](3,8);
|
||||
s.SetShade(Color(5,4,3)*Random[ExploS](20,40));
|
||||
}
|
||||
Spawn("SWWMCrushedSpike",source.pos);
|
||||
if ( source.master ) source.master.Destroy();
|
||||
source.Destroy();
|
||||
damage = 20; // reduce so it's not instant kill
|
||||
SWWMUtility.MarkAchievement("kancho",player);
|
||||
}
|
||||
}
|
||||
// no damage whatsoever
|
||||
if ( scriptedinvul )
|
||||
return 0;
|
||||
if ( damage <= 0 ) return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
|
||||
if ( !inflictor && !source && (FloorSector.flags&Sector.SECF_ENDLEVEL) )
|
||||
{
|
||||
// end level hax
|
||||
damage = max(50,health-100);
|
||||
flags |= DMG_FORCED|DMG_NO_ARMOR;
|
||||
mod = 'EndLevel';
|
||||
}
|
||||
int oldpchance = PainChance;
|
||||
if ( damage < 5 ) PainChance = 0;
|
||||
int realdmg = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
|
||||
if ( lastdamagetic != gametic )
|
||||
{
|
||||
lastdamage = 0;
|
||||
lastdamagetimer = 0;
|
||||
}
|
||||
lastdamage += realdmg;
|
||||
lastdamagetic = gametic;
|
||||
lastdamagetimer = max(lastdamagetimer,lastdamagetic+clamp(lastdamage/2,10,40));
|
||||
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
|
||||
{
|
||||
lastmpain = level.maptime;
|
||||
if ( player && (player.mo == self) ) A_DemoPain();
|
||||
}
|
||||
PainChance = oldpchance;
|
||||
if ( (Health <= 0) && (source == self) && (flags&DMG_EXPLOSION) )
|
||||
SWWMUtility.MarkAchievement("dime",player);
|
||||
facedamage = true;
|
||||
return realdmg;
|
||||
}
|
||||
|
||||
private Vector2 BobWeaponAngle( double ticfrac )
|
||||
{
|
||||
// bob with player view
|
||||
Vector2 prevbob = (-sin(oldbobtime*180.),.5*abs(sin(oldbobtime*180.)))*oldbob*.25;
|
||||
Vector2 bob = (-sin(bobtime*180.),.5*abs(sin(bobtime*180.)))*player.bob*.25;
|
||||
double bobstr = (player.WeaponState&WF_WEAPONBOBBING)?1.:player.GetWBobFire();
|
||||
return SWWMUtility.LerpVector2(prevbob,bob,ticfrac)*bobstr*clamp(viewbob,0.,1.5);
|
||||
}
|
||||
|
||||
override Vector2 BobWeapon( double ticfrac )
|
||||
{
|
||||
// non-mod weapons bob normally
|
||||
if ( !(player.ReadyWeapon is 'SWWMWeapon') )
|
||||
return Super.BobWeapon(ticfrac);
|
||||
bool oldbob = !!(player.WeaponState&WF_WEAPONBOBBING);
|
||||
player.WeaponState |= WF_WEAPONBOBBING; // always bob
|
||||
Vector2 cur = BobWeaponAngle(ticfrac);
|
||||
if ( !oldbob ) player.WeaponState &= ~WF_WEAPONBOBBING;
|
||||
double fangle = SWWMUtility.Lerp(oldangle,angle,ticfrac);
|
||||
double fpitch = SWWMUtility.Lerp(oldpitch,pitch,ticfrac);
|
||||
double flagangle = SWWMUtility.Lerp(oldlagangle,lagangle,ticfrac);
|
||||
double flagpitch = SWWMUtility.Lerp(oldlagpitch,lagpitch,ticfrac);
|
||||
double diffang = fangle-flagangle;
|
||||
double diffpitch = fpitch-flagpitch;
|
||||
if ( abs(diffang) > 1. )
|
||||
{
|
||||
int sgn = (diffang>0)?1:-1;
|
||||
diffang = abs(diffang)**.7*sgn;
|
||||
}
|
||||
if ( abs(diffpitch) > 1. )
|
||||
{
|
||||
int sgn = (diffpitch>0)?1:-1;
|
||||
diffpitch = abs(diffpitch)**.7*sgn;
|
||||
}
|
||||
Vector3 flagvel = SWWMUtility.LerpVector3(oldlagvel,lagvel,ticfrac);
|
||||
double diffx = flagvel dot SWWMUtility.AngleToVector3(flagangle+90);
|
||||
double diffy = flagvel dot (0,0,1);
|
||||
if ( abs(diffx) > 1. )
|
||||
{
|
||||
int sgn = (diffx>0)?1:-1;
|
||||
diffx = abs(diffx)**.5*sgn;
|
||||
}
|
||||
if ( abs(diffy) > 1. )
|
||||
{
|
||||
int sgn = (diffy>0)?1:-1;
|
||||
diffy = abs(diffy)**.5*sgn;
|
||||
}
|
||||
cur.x += diffang*.4;
|
||||
cur.y -= diffpitch*.4;
|
||||
cur.x += diffx*.8;
|
||||
cur.y += diffy*.8;
|
||||
return cur*SWWMUtility.Lerp(oldlagready,lagready,ticfrac);
|
||||
}
|
||||
|
||||
override bool OnGiveSecret( bool printmsg, bool playsound )
|
||||
{
|
||||
if ( !player ) return false;
|
||||
int score = 100;
|
||||
// last secret (this is called before counting it up, so have to subtract)
|
||||
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
||||
if ( !deathmatch && !(gameinfo.gametype&GAME_Hexen) && (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
|
||||
{
|
||||
if ( hnd ) hnd.allsecrets = true;
|
||||
score = 1000;
|
||||
if ( player == players[consoleplayer] )
|
||||
{
|
||||
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),score);
|
||||
SWWMHandler.AddOneliner("findsecret",2,40);
|
||||
facegrin = true;
|
||||
}
|
||||
else Console.Printf(StringTable.Localize("$SWWM_LASTSECRETREM"),player.GetUserName(),score);
|
||||
SWWMUtility.AchievementProgressInc("allsecrets",1,player);
|
||||
}
|
||||
else if ( player == players[consoleplayer] )
|
||||
{
|
||||
Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),score);
|
||||
SWWMHandler.AddOneliner("findsecret",2,40);
|
||||
facegrin = true;
|
||||
}
|
||||
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRETREM"),player.GetUserName(),score);
|
||||
SWWMCredits.Give(player,score);
|
||||
SWWMScoreObj.Spawn(score,Vec3Offset(0,0,Height/2));
|
||||
// somehow ongivesecret can be called BEFORE PostBeginPlay (what the fuck)
|
||||
if ( !mystats ) mystats = SWWMStats.Find(player);
|
||||
mystats.secrets++;
|
||||
return true;
|
||||
}
|
||||
|
||||
override bool Used( Actor user )
|
||||
{
|
||||
if ( !(user is 'Demolitionist') || !player || (player.mo != self) ) return false;
|
||||
if ( (user.player == players[consoleplayer]) && (health > 0) )
|
||||
{
|
||||
SWWMHandler.AddOneliner("greet",2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
override void PreTravelled()
|
||||
{
|
||||
// clean up attached actors
|
||||
if ( selflight ) selflight.Destroy();
|
||||
if ( myshadow ) myshadow.Destroy();
|
||||
// clean up our objects
|
||||
SWWMItemSense si = itemsense;
|
||||
while ( si )
|
||||
{
|
||||
let next = si.next;
|
||||
si.Destroy();
|
||||
si = next;
|
||||
}
|
||||
SWWMMagItem mi = magitem;
|
||||
while ( mi )
|
||||
{
|
||||
let next = mi.next;
|
||||
mi.Destroy();
|
||||
mi = next;
|
||||
}
|
||||
magitem_cnt = 0;
|
||||
// disable death exits
|
||||
if ( player && (player.playerstate == PST_DEAD) )
|
||||
{
|
||||
player.cheats &= ~(CF_FROZEN|CF_TOTALLYFROZEN);
|
||||
player.Resurrect();
|
||||
player.damagecount = 0;
|
||||
player.bonuscount = 0;
|
||||
player.poisoncount = 0;
|
||||
roll = 0;
|
||||
if ( special1 > 2 ) special1 = 0;
|
||||
}
|
||||
// inventory wipes
|
||||
if ( invwipe && (player.playerstate != PST_DEAD) )
|
||||
{
|
||||
bool wiped = false;
|
||||
bool resetammo = false;
|
||||
bool resetitems = false;
|
||||
bool resethealth = false;
|
||||
if ( invwipe&WIPE_EPISODE )
|
||||
{
|
||||
SWWMUtility.WipeInventory(self,swwm_resetscore);
|
||||
wiped = true;
|
||||
}
|
||||
if ( invwipe&WIPE_CLUSTER )
|
||||
{
|
||||
if ( (swwm_ps_fullreset == 2) && !wiped )
|
||||
{
|
||||
SWWMUtility.WipeInventory(self,swwm_resetscore);
|
||||
wiped = true;
|
||||
}
|
||||
if ( (swwm_ps_resetammo == 2) && !wiped )
|
||||
{
|
||||
SWWMUtility.ResetAmmo(self);
|
||||
resetammo = true;
|
||||
}
|
||||
if ( (swwm_ps_resetitems == 2) && !wiped )
|
||||
{
|
||||
SWWMUtility.ResetItems(self);
|
||||
resetitems = true;
|
||||
}
|
||||
if ( (swwm_ps_resethealth == 2) && !wiped )
|
||||
{
|
||||
SWWMUtility.ResetHealth(self);
|
||||
resethealth = true;
|
||||
}
|
||||
}
|
||||
if ( invwipe&WIPE_MAP )
|
||||
{
|
||||
if ( (swwm_ps_fullreset == 1) && !wiped )
|
||||
{
|
||||
SWWMUtility.WipeInventory(self,swwm_resetscore);
|
||||
wiped = true;
|
||||
}
|
||||
if ( (swwm_ps_resetammo == 1) && !wiped && !resetammo )
|
||||
SWWMUtility.ResetAmmo(self);
|
||||
if ( (swwm_ps_resetitems == 1) && !wiped && !resetitems )
|
||||
SWWMUtility.ResetItems(self);
|
||||
if ( (swwm_ps_resethealth == 1) && !wiped && !resethealth )
|
||||
SWWMUtility.ResetHealth(self);
|
||||
}
|
||||
}
|
||||
invwipe = 0;
|
||||
}
|
||||
|
||||
override void Travelled()
|
||||
{
|
||||
// reinitialize
|
||||
dashfuel = default.dashfuel;
|
||||
last_boost = 0;
|
||||
last_kick = 0;
|
||||
hasrevived = false;
|
||||
blinktime = 30;
|
||||
// cancel dash/boost
|
||||
A_StopSound(CHAN_JETPACK);
|
||||
fuelcooldown = 0.;
|
||||
dashcooldown = 0.;
|
||||
dashboost = 0.;
|
||||
// prevent sudden stomping if we were previously falling
|
||||
lastvelz = vel.z;
|
||||
// early cancel gestures
|
||||
if ( player )
|
||||
{
|
||||
if ( player.ReadyWeapon is 'SWWMItemGesture' )
|
||||
player.ReadyWeapon = SWWMItemGesture(player.ReadyWeapon).gest;
|
||||
if ( player.ReadyWeapon is 'SWWMGesture' )
|
||||
{
|
||||
player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon;
|
||||
player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect"));
|
||||
}
|
||||
}
|
||||
// re-add ourselves to the "suckable list" (otherwise the Ynykron Singularity won't hurt us)
|
||||
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
||||
if ( hnd && (hnd.SuckableActors.Find(self) >= hnd.SuckableActors.Size()) )
|
||||
hnd.SuckableActors.Push(self);
|
||||
}
|
||||
|
||||
override bool PreTeleport( Vector3 destpos, double destangle, int flags )
|
||||
{
|
||||
// store old pos
|
||||
pretelepos = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
override void PostTeleport( Vector3 destpos, double destangle, int flags )
|
||||
{
|
||||
hasteleported = true; // notify tick that we teleported, so it ignores the travel distance
|
||||
mystats.teledist += level.Vec3Diff(pretelepos,pos).length();
|
||||
// reset all smooth bob variables if angles/velocity aren't carried over
|
||||
if ( !(flags&TELF_KEEPORIENTATION) )
|
||||
{
|
||||
oldlagangle = lagangle = oldangle = angle;
|
||||
oldlagpitch = lagpitch = oldpitch = pitch;
|
||||
}
|
||||
if ( !(flags&TELF_KEEPVELOCITY) )
|
||||
{
|
||||
oldlagvel = lagvel = vel;
|
||||
lastvelz = vel.z;
|
||||
}
|
||||
// notify carried lamp that we just moved
|
||||
let l = SWWMLamp(FindInventory("SWWMLamp"));
|
||||
if ( l && l.thelamp )
|
||||
CompanionLamp(l.thelamp).justteleport = true;
|
||||
}
|
||||
|
||||
override void MarkPrecacheSounds()
|
||||
{
|
||||
Super.MarkPrecacheSounds();
|
||||
MarkSound("demolitionist/walk1");
|
||||
MarkSound("demolitionist/walk2");
|
||||
MarkSound("demolitionist/walk3");
|
||||
MarkSound("demolitionist/walk4");
|
||||
MarkSound("demolitionist/runstart1");
|
||||
MarkSound("demolitionist/runstart2");
|
||||
MarkSound("demolitionist/runstart3");
|
||||
MarkSound("demolitionist/runstart4");
|
||||
MarkSound("demolitionist/run1");
|
||||
MarkSound("demolitionist/run2");
|
||||
MarkSound("demolitionist/run3");
|
||||
MarkSound("demolitionist/run4");
|
||||
MarkSound("demolitionist/runstop1");
|
||||
MarkSound("demolitionist/runstop2");
|
||||
MarkSound("demolitionist/runstop3");
|
||||
MarkSound("demolitionist/runstop4");
|
||||
MarkSound("demolitionist/jet");
|
||||
MarkSound("demolitionist/jetstop");
|
||||
MarkSound("demolitionist/death1");
|
||||
MarkSound("demolitionist/death2");
|
||||
MarkSound("demolitionist/death3");
|
||||
MarkSound("demolitionist/xdeath1");
|
||||
MarkSound("demolitionist/xdeath2");
|
||||
MarkSound("demolitionist/xdeath3");
|
||||
MarkSound("demolitionist/wdeath1");
|
||||
MarkSound("demolitionist/wdeath2");
|
||||
MarkSound("demolitionist/wdeath3");
|
||||
MarkSound("demolitionist/pain1");
|
||||
MarkSound("demolitionist/pain2");
|
||||
MarkSound("demolitionist/pain3");
|
||||
MarkSound("demolitionist/hipain1");
|
||||
MarkSound("demolitionist/hipain2");
|
||||
MarkSound("demolitionist/hipain3");
|
||||
MarkSound("demolitionist/lopain1");
|
||||
MarkSound("demolitionist/lopain2");
|
||||
MarkSound("demolitionist/lopain3");
|
||||
MarkSound("demolitionist/hardland1");
|
||||
MarkSound("demolitionist/hardland2");
|
||||
MarkSound("demolitionist/hardland3");
|
||||
MarkSound("demolitionist/swing1");
|
||||
MarkSound("demolitionist/swing2");
|
||||
MarkSound("demolitionist/swing3");
|
||||
MarkSound("demolitionist/wswing1");
|
||||
MarkSound("demolitionist/wswing2");
|
||||
MarkSound("demolitionist/punch1");
|
||||
MarkSound("demolitionist/punch2");
|
||||
MarkSound("demolitionist/punch3");
|
||||
MarkSound("demolitionist/punchf1");
|
||||
MarkSound("demolitionist/punchf2");
|
||||
MarkSound("demolitionist/punchf3");
|
||||
MarkSound("demolitionist/bump1");
|
||||
MarkSound("demolitionist/bump2");
|
||||
MarkSound("demolitionist/bump3");
|
||||
MarkSound("demolitionist/kick1");
|
||||
MarkSound("demolitionist/kick2");
|
||||
MarkSound("demolitionist/kick3");
|
||||
MarkSound("demolitionist/revive");
|
||||
MarkSound("demolitionist/youdied");
|
||||
MarkSound("demolitionist/parry");
|
||||
MarkSound("demolitionist/handsup");
|
||||
MarkSound("demolitionist/handsdown");
|
||||
MarkSound("demolitionist/whits1");
|
||||
MarkSound("demolitionist/whits2");
|
||||
MarkSound("demolitionist/whits3");
|
||||
MarkSound("demolitionist/whitm1");
|
||||
MarkSound("demolitionist/whitm2");
|
||||
MarkSound("demolitionist/whitm3");
|
||||
MarkSound("demolitionist/whitl1");
|
||||
MarkSound("demolitionist/whitl2");
|
||||
MarkSound("demolitionist/buttslam");
|
||||
MarkSound("demolitionist/buttslamx");
|
||||
MarkSound("demolitionist/smooch");
|
||||
MarkSound("demolitionist/blowkiss");
|
||||
MarkSound("demolitionist/petting");
|
||||
MarkSound("demolitionist/knockout");
|
||||
}
|
||||
}
|
||||
954
zscript/player/swwm_player_anim.zsc
Normal file
954
zscript/player/swwm_player_anim.zsc
Normal file
|
|
@ -0,0 +1,954 @@
|
|||
// animations and states
|
||||
extend Class Demolitionist
|
||||
{
|
||||
override void PlayIdle()
|
||||
{
|
||||
if ( !player )
|
||||
{
|
||||
if ( !InStateSequence(CurState,FindState("Spawn")) )
|
||||
SetStateLabel("Spawn");
|
||||
return;
|
||||
}
|
||||
if ( player.health <= 0 ) return;
|
||||
if ( !bNoGravity && player.onground )
|
||||
{
|
||||
// Ground
|
||||
if ( player.crouchdir == -1 )
|
||||
{
|
||||
// Crouching
|
||||
if ( InStateSequence(CurState,FindState("CrouchMove"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("Crouch");
|
||||
else if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Float"))
|
||||
|| InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("StartCrouch");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Crouch"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMove"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("EndCrouch");
|
||||
else if ( InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("Float")) )
|
||||
{
|
||||
SetStateLabel("Spawn");
|
||||
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
||||
}
|
||||
else if ( InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop")) )
|
||||
SetStateLabel("SeeFastEnd");
|
||||
else if ( InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("SwimEnd");
|
||||
}
|
||||
}
|
||||
else if ( !bNoGravity )
|
||||
{
|
||||
// Falling
|
||||
if ( player.crouchdir == -1 )
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Jump"))
|
||||
|| InStateSequence(CurState,FindState("Float"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop"))
|
||||
|| InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("StartCrouch");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( (InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Float")))
|
||||
&& (abs(pos.z-floorz) > maxstepheight) )
|
||||
SetStateLabel("Fall");
|
||||
else if ( InStateSequence(CurState,FindState("Crouch"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMove"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("EndCrouch");
|
||||
else if ( InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("SwimEnd");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Swimming
|
||||
if ( player.crouchdir == -1 )
|
||||
{
|
||||
// Crouching
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Jump"))
|
||||
|| InStateSequence(CurState,FindState("Float"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop"))
|
||||
|| InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("StartCrouch");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Jump"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop")) )
|
||||
SetStateLabel("Float");
|
||||
else if ( InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("SwimEnd");
|
||||
else if ( InStateSequence(CurState,FindState("Crouch"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMove"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("EndCrouch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override void PlayRunning()
|
||||
{
|
||||
if ( !player )
|
||||
{
|
||||
if ( !InStateSequence(CurState,FindState("SeeRun")) )
|
||||
SetStateLabel("SeeRun");
|
||||
return;
|
||||
}
|
||||
if ( player.health <= 0 ) return;
|
||||
if ( !bNoGravity && player.onground )
|
||||
{
|
||||
// Ground
|
||||
if ( player.crouchdir == -1 )
|
||||
{
|
||||
// Crouching
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop"))
|
||||
|| InStateSequence(CurState,FindState("Float"))
|
||||
|| InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("StartCrouch");
|
||||
else if ( InStateSequence(CurState,FindState("Crouch")) )
|
||||
{
|
||||
switch( FastCheck() )
|
||||
{
|
||||
case 2:
|
||||
SetStateLabel("CrouchMoveFast");
|
||||
break;
|
||||
case 1:
|
||||
SetStateLabel("CrouchMoveRun");
|
||||
break;
|
||||
default:
|
||||
SetStateLabel("CrouchMove");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Crouch"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMove"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("EndCrouch");
|
||||
else if ( InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("SwimEnd");
|
||||
else if ( (FastCheck() == 2)
|
||||
&& (InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))) )
|
||||
SetStateLabel("SeeFast");
|
||||
else if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Float"))
|
||||
|| InStateSequence(CurState,FindState("Turn")) )
|
||||
{
|
||||
if ( FastCheck() == 1 ) SetStateLabel("SeeRun");
|
||||
else SetStateLabel("See");
|
||||
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( !bNoGravity )
|
||||
{
|
||||
// Falling
|
||||
PlayIdle();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Swimming
|
||||
if ( player.crouchdir == -1 )
|
||||
{
|
||||
// Crouching
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Jump"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop"))
|
||||
|| InStateSequence(CurState,FindState("Float"))
|
||||
|| InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("StartCrouch");
|
||||
else if ( InStateSequence(CurState,FindState("Crouch")) )
|
||||
{
|
||||
switch( FastCheck() )
|
||||
{
|
||||
case 2:
|
||||
SetStateLabel("CrouchMoveFast");
|
||||
break;
|
||||
case 1:
|
||||
SetStateLabel("CrouchMoveRun");
|
||||
break;
|
||||
default:
|
||||
SetStateLabel("CrouchMove");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( bFlyCheat || (player.cheats&CF_NOCLIP2) )
|
||||
{
|
||||
// Special case, fly cheats don't play a swim animation, only float
|
||||
// (this fixes Demo "swimming" on the library ladder in Spooktober, for example)
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Jump"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop"))
|
||||
|| InStateSequence(CurState,FindState("Swim"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoop"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|
||||
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
|
||||
SetStateLabel("Float");
|
||||
else if ( InStateSequence(CurState,FindState("Crouch"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMove"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("EndCrouch");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Spawn"))
|
||||
|| InStateSequence(CurState,FindState("Turn"))
|
||||
|| InStateSequence(CurState,FindState("See"))
|
||||
|| InStateSequence(CurState,FindState("SeeRun"))
|
||||
|| InStateSequence(CurState,FindState("SeeFast"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
||||
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
||||
|| InStateSequence(CurState,FindState("Jump"))
|
||||
|| InStateSequence(CurState,FindState("Fall"))
|
||||
|| InStateSequence(CurState,FindState("FallLoop"))
|
||||
|| InStateSequence(CurState,FindState("Float")) )
|
||||
SetStateLabel("Swim");
|
||||
else if ( InStateSequence(CurState,FindState("Crouch"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|
||||
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
|
||||
SetStateLabel("EndCrouch");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override void PlayAttacking()
|
||||
{
|
||||
// Do nothing if it's a SWWM weapon, since those do things themselves
|
||||
if ( player && (player.ReadyWeapon is 'SWWMWeapon') )
|
||||
return;
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
|
||||
else SetStateLabel("Missile");
|
||||
}
|
||||
|
||||
override void PlayAttacking2()
|
||||
{
|
||||
PlayAttacking();
|
||||
}
|
||||
|
||||
void PlayFire()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
|
||||
else SetStateLabel("Missile");
|
||||
}
|
||||
|
||||
void PlayMelee()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMelee");
|
||||
else SetStateLabel("Melee");
|
||||
}
|
||||
|
||||
void PlayFastMelee()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastMelee");
|
||||
else SetStateLabel("FastMelee");
|
||||
}
|
||||
|
||||
void PlayReload()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchReload");
|
||||
else SetStateLabel("Reload");
|
||||
}
|
||||
|
||||
void PlayFastReload()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastReload");
|
||||
else SetStateLabel("FastReload");
|
||||
}
|
||||
|
||||
void PlayCheckGun()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash"))
|
||||
|| InStateSequence(CurState,FindState("Boost")) )
|
||||
return; // don't cancel dash/boost
|
||||
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchCheckGun");
|
||||
else SetStateLabel("CheckGun");
|
||||
}
|
||||
|
||||
void A_DMFade()
|
||||
{
|
||||
if ( player ) return;
|
||||
Spawn("TeleportFog",pos,ALLOW_REPLACE);
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void A_DemoPain()
|
||||
{
|
||||
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
|
||||
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
|
||||
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
||||
int loudlv = swwm_voiceamp;
|
||||
if ( lastdamage > 70 )
|
||||
{
|
||||
A_QuakeEx(3,3,3,9,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
A_StartSound("demolitionist/hipain",CHAN_VOICE);
|
||||
lastbump *= 1.04;
|
||||
if ( swwm_mutevoice < 4 )
|
||||
{
|
||||
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
}
|
||||
}
|
||||
else if ( lastdamage > 30 )
|
||||
{
|
||||
A_QuakeEx(2,2,2,6,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
A_StartSound("demolitionist/pain",CHAN_VOICE);
|
||||
lastbump *= 1.02;
|
||||
if ( swwm_mutevoice < 4 )
|
||||
{
|
||||
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
}
|
||||
}
|
||||
else if ( lastdamage > 0 )
|
||||
{
|
||||
A_QuakeEx(1,1,1,3,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
A_StartSound("demolitionist/lopain",CHAN_VOICE);
|
||||
lastbump *= 1.01;
|
||||
if ( swwm_mutevoice < 4 )
|
||||
{
|
||||
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void A_DemoScream()
|
||||
{
|
||||
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
|
||||
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
|
||||
A_StopSound(CHAN_DEMOVOICE);
|
||||
A_StopSound(CHAN_DEMOVOICEAUX);
|
||||
A_StopSound(CHAN_DEMOVOICEAUX2);
|
||||
A_StopSound(CHAN_DEMOVOICEAUX3);
|
||||
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
||||
int loudlv = swwm_voiceamp;
|
||||
Sound snd = "demolitionist/death";
|
||||
if ( special1 < 10 )
|
||||
snd = "demolitionist/wdeath";
|
||||
if ( health < -50 )
|
||||
snd = "demolitionist/xdeath";
|
||||
A_StartSound(snd,CHAN_VOICE);
|
||||
if ( swwm_mutevoice < 4 )
|
||||
{
|
||||
A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
}
|
||||
}
|
||||
|
||||
void A_Dash()
|
||||
{
|
||||
vel += dashdir*dashboost*clamp(dashfuel/20.,0.,1.);
|
||||
player.vel *= 0.;
|
||||
if ( dashboost < .2 ) dashboost = 0.;
|
||||
else if ( !(player.cmd.buttons&BT_USER2) ) dashboost *= .1;
|
||||
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
|
||||
SWWMUtility.AchievementProgressIncDouble("fuel",fueluse,player);
|
||||
mystats.fuelusage += fueluse;
|
||||
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
|
||||
dashcooldown = min(40,max(10,int(dashcooldown*1.4)));
|
||||
dashlockst = clamp(dashcooldown/3,2,10);
|
||||
fuelcooldown = max(30,fuelcooldown);
|
||||
if ( (dashfuel <= 0.) && fullfuel )
|
||||
SWWMUtility.AchievementProgressInc("brake",1,player);
|
||||
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
|
||||
SetStateLabel("DashEnd");
|
||||
}
|
||||
|
||||
void A_BoostUp( bool initial = false )
|
||||
{
|
||||
vel.z += .25*dashboost*clamp(dashfuel/20.,0,1.);
|
||||
player.vel *= 0.;
|
||||
if ( dashboost < .2 ) dashboost = 0.;
|
||||
else
|
||||
{
|
||||
if ( player.cmd.buttons&BT_JUMP ) dashboost = min(5.,dashboost*1.01);
|
||||
else dashboost *= .4;
|
||||
last_boost = level.maptime+1;
|
||||
}
|
||||
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
|
||||
SWWMUtility.AchievementProgressIncDouble("fuel",fueluse,player);
|
||||
mystats.fuelusage += fueluse;
|
||||
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
|
||||
if ( ((dashfuel <= 0.) || (dashboost <= 0.)) )
|
||||
{
|
||||
if ( !initial )
|
||||
{
|
||||
if ( player.onground ) SetStateLabel("JumpEnd");
|
||||
else SetStateLabel("Fall");
|
||||
}
|
||||
return;
|
||||
}
|
||||
fuelcooldown = max(20,fuelcooldown);
|
||||
}
|
||||
|
||||
void A_Footstep( double yofs, int run = 0, double vol = .3, bool nosplash = false )
|
||||
{
|
||||
if ( run == 2 )
|
||||
{
|
||||
A_StartSound("demolitionist/run",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
|
||||
if ( !nosplash )
|
||||
{
|
||||
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
|
||||
b.target = self;
|
||||
b.A_CheckTerrain();
|
||||
}
|
||||
vel.xy += (RotateVector(NormalizedMove(),angle)/3600.)*TweakSpeed();
|
||||
}
|
||||
else if ( run == 1 )
|
||||
{
|
||||
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
|
||||
if ( !nosplash )
|
||||
{
|
||||
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
|
||||
b.target = self;
|
||||
b.A_CheckTerrain();
|
||||
}
|
||||
vel.xy += (RotateVector(NormalizedMove(),angle)/4800.)*TweakSpeed();
|
||||
}
|
||||
else
|
||||
{
|
||||
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.2);
|
||||
if ( !nosplash )
|
||||
{
|
||||
let b = Spawn("SmolInvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
|
||||
b.target = self;
|
||||
b.A_CheckTerrain();
|
||||
}
|
||||
vel.xy += (RotateVector(NormalizedMove(),angle)/6400.)*TweakSpeed();
|
||||
}
|
||||
}
|
||||
|
||||
int FastCheck()
|
||||
{
|
||||
if ( !player ) return 1;
|
||||
if ( bWalking || (NormalizedMove().length() <= 8000.) ) return 0; // walk
|
||||
if ( player.cmd.buttons&BT_RUN ) return 2; // sprint
|
||||
return 1; // walk
|
||||
}
|
||||
|
||||
State A_FastJump( StateLabel walk = null, StateLabel run = null, StateLabel sprint = null )
|
||||
{
|
||||
int fc = FastCheck();
|
||||
if ( (fc == 2) && sprint ) return ResolveState(sprint);
|
||||
if ( (fc == 1) && run ) return ResolveState(run);
|
||||
if ( (fc == 0) && walk ) return ResolveState(walk);
|
||||
return ResolveState(null);
|
||||
}
|
||||
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
// normal idle
|
||||
#### # 2;
|
||||
XZW1 A 1 A_JumpIf(player&&(player.mo==self)&&(abs(player.cmd.yaw|player.cmd.pitch)>128),"Turn");
|
||||
Wait;
|
||||
See:
|
||||
// normal walking
|
||||
#### # 2;
|
||||
XZW1 BCD 2 A_FastJump(null,"SeeRun","SeeFast");
|
||||
XZW1 E 0 A_Footstep(1);
|
||||
XZW1 EFGHIJKL 2 A_FastJump(null,"SeeRun","SeeFast");
|
||||
XZW1 M 0 A_Footstep(-1);
|
||||
XZW1 MNOPA 2 A_FastJump(null,"SeeRun","SeeFast");
|
||||
Goto See+1;
|
||||
Turn:
|
||||
#### # 8 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
||||
XZW1 C 1 A_JumpIf(!player||!(player.cmd.yaw|player.cmd.pitch),1);
|
||||
Wait;
|
||||
XZW1 C 3 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
||||
Goto Spawn+1;
|
||||
SeeRun:
|
||||
#### # 2;
|
||||
XZWI PQR 2 A_FastJump("See",null,"SeeFast");
|
||||
XZWI S 0 A_Footstep(1,1);
|
||||
XZWI STUVWX 2 A_FastJump("See",null,"SeeFast");
|
||||
XZWI Y 0 A_Footstep(-1,1);
|
||||
XZWI YZ 2 A_FastJump("See",null,"SeeFast");
|
||||
XZW1 A 2 A_FastJump("See",null,"SeeFast");
|
||||
Goto SeeRun+1;
|
||||
SeeFast:
|
||||
// sprinting
|
||||
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
|
||||
XZW1 QRS 2;
|
||||
Goto SeeFastLoop;
|
||||
SeeFastLoop:
|
||||
// keep sprinting
|
||||
XZW1 T 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW1 U 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW1 V 0 A_Footstep(1,2);
|
||||
XZW1 V 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW1 W 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW1 X 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW1 Y 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW1 Z 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 A 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 B 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 C 0 A_Footstep(-1,2);
|
||||
XZW2 C 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 D 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 E 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 F 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
XZW2 G 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
|
||||
Goto SeeFastLoop;
|
||||
SeeFastEnd:
|
||||
// brake
|
||||
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
|
||||
XZW2 HIJKL 2;
|
||||
Goto Spawn+1;
|
||||
Pain:
|
||||
// ouchy
|
||||
XZW1 A 1 A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchPain");
|
||||
XZW2 M 1 A_DemoPain();
|
||||
XZW2 NOPQ 1;
|
||||
Goto Spawn+1;
|
||||
Death:
|
||||
XDeath:
|
||||
// ded
|
||||
XZW1 A 0 A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchDeath");
|
||||
XZW1 A 2
|
||||
{
|
||||
A_DemoScream();
|
||||
bSOLID = true; // we need this to avoid clipping into things
|
||||
}
|
||||
XZW2 RSTUVWXYZ 2;
|
||||
XZW3 ABCDEFG 2;
|
||||
XZW3 H 1 A_DMFade();
|
||||
Wait;
|
||||
Boost:
|
||||
// start boost
|
||||
#### # 2;
|
||||
XZW3 IJKLMNO 2
|
||||
{
|
||||
if ( player.onground||bNoGravity )
|
||||
return ResolveState("BoostEnd");
|
||||
A_BoostUp(true);
|
||||
return ResolveState(null);
|
||||
}
|
||||
// keep boost
|
||||
XZW3 P 1
|
||||
{
|
||||
if ( player.onground||bNoGravity )
|
||||
return ResolveState("BoostEnd");
|
||||
A_BoostUp(false);
|
||||
return ResolveState(null);
|
||||
}
|
||||
XZW3 P 1 A_JumpIf((vel.z<-10)&&(pos.z>(floorz+80)),"Fall");
|
||||
Goto Boost+8;
|
||||
BoostEnd:
|
||||
// stop boost
|
||||
#### # 2;
|
||||
XZW3 PQRSTUVW 2;
|
||||
Goto Spawn+1;
|
||||
Jump:
|
||||
#### # 2;
|
||||
XZWJ ABCDEF 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
|
||||
Goto FallLoop;
|
||||
Fall:
|
||||
// start fall
|
||||
#### # 4;
|
||||
XZW3 XYZ 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
|
||||
XZW4 AB 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
|
||||
Goto FallLoop;
|
||||
FallLoop:
|
||||
// falling
|
||||
XZW4 CDEFGH 3 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
|
||||
Goto FallLoop;
|
||||
FallEnd:
|
||||
// landing
|
||||
XZW4 CIJKLMN 2;
|
||||
Goto Spawn+1;
|
||||
Dash:
|
||||
#### # 2;
|
||||
XZW4 O 2 A_Dash();
|
||||
XZW4 PQRS 2 A_Dash();
|
||||
Goto Dash+2;
|
||||
DashEnd:
|
||||
XZW4 TUVWX 2;
|
||||
Goto Spawn+1;
|
||||
Wave:
|
||||
#### # 3;
|
||||
XZW4 YZ 3;
|
||||
XZW5 ABCDEFGHIJKLM 3;
|
||||
Goto Spawn+1;
|
||||
Approve:
|
||||
#### # 3;
|
||||
XZW5 NOPQR 3;
|
||||
XZW5 S 3 { facegrin = true; }
|
||||
XZW5 TUVWXYZ 3;
|
||||
XZW6 ABCD 3;
|
||||
Goto Spawn+1;
|
||||
Victory:
|
||||
#### # 3;
|
||||
XZW6 EFGH 3;
|
||||
XZW6 I 3 { facegrin = true; }
|
||||
XZW6 JKLMNOPQRSTUVW 3;
|
||||
Goto Spawn+1;
|
||||
BlowKiss:
|
||||
#### # 3;
|
||||
XZWD EFGH 3;
|
||||
XZWD I 3 { faceblink = true; }
|
||||
XZWD JKLMNO 3;
|
||||
XZWD P 3 { facewink = true; }
|
||||
XZWD QRSTUVW 3;
|
||||
Goto Spawn+1;
|
||||
Headpat:
|
||||
#### # 3;
|
||||
XZWH ST 3;
|
||||
XZWH U 2;
|
||||
XZWH V 2 { facegrin = true; }
|
||||
XZWH W 2;
|
||||
XZWH XYZ 1;
|
||||
XZWI A 1;
|
||||
XZWI B 2;
|
||||
XZWI C 2 { facegrin = true; }
|
||||
XZWI DE 2;
|
||||
XZWI FG 1;
|
||||
HeadpatLoop:
|
||||
XZWI H 1;
|
||||
XZWH XYZ 1;
|
||||
XZWI A 1;
|
||||
XZWI B 2;
|
||||
XZWI C 2 { facegrin = true; }
|
||||
XZWI DE 2;
|
||||
XZWI FGH 1;
|
||||
XZWI IJK 2;
|
||||
XZWI LMNO 3;
|
||||
Goto Spawn+1;
|
||||
Ragepat:
|
||||
#### # 3;
|
||||
XZWH ST 2;
|
||||
XZWH UVW 1;
|
||||
XZWH XZ 1;
|
||||
XZWI BCDE 1;
|
||||
XZWI FH 1;
|
||||
XZWH XZ 1;
|
||||
XZWI BCDE 1;
|
||||
XZWI FH 1;
|
||||
XZWH XZ 1;
|
||||
XZWI BCDE 1;
|
||||
XZWI F 1;
|
||||
RagepatLoop:
|
||||
XZWI H 1;
|
||||
XZWH XZ 1;
|
||||
XZWI BCDE 1;
|
||||
XZWI FH 1;
|
||||
XZWI IJK 1;
|
||||
XZWI LMNO 2;
|
||||
Goto Spawn+1;
|
||||
Missile:
|
||||
// attacking
|
||||
#### # 2;
|
||||
XZW6 XYZ 2;
|
||||
XZW7 ABC 2;
|
||||
Goto Spawn+1;
|
||||
Melee:
|
||||
// ponch
|
||||
#### # 2;
|
||||
XZW8 TUVWXYZ 2;
|
||||
XZW9 ABCDEF 2;
|
||||
Goto Spawn+1;
|
||||
FastMelee:
|
||||
// ponch (fast)
|
||||
#### # 2;
|
||||
XZW8 TUVWXYZ 1;
|
||||
XZW9 ABCDE 1;
|
||||
XZW9 F 2;
|
||||
Goto Spawn+1;
|
||||
Reload:
|
||||
// reload
|
||||
#### # 2;
|
||||
XZW9 GHIJKLMNOPQRSTUVWXYZ 2;
|
||||
XZWA ABCDE 2;
|
||||
Goto Spawn+1;
|
||||
FastReload:
|
||||
// reload (fast)
|
||||
#### # 2;
|
||||
XZW9 GHIJKLMNOPQRSTUVWXYZ 1;
|
||||
XZWA ABCDE 1;
|
||||
Goto Spawn+1;
|
||||
CheckGun:
|
||||
// speen
|
||||
#### # 2;
|
||||
XZWA FGHIJKLMNOPQRSTUVWXY 2;
|
||||
Goto Spawn+1;
|
||||
StartCrouch:
|
||||
// go crouching
|
||||
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
|
||||
XZW7 DEFGH 1;
|
||||
XZW7 IJKL 2;
|
||||
Goto Crouch+1;
|
||||
Crouch:
|
||||
#### # 4;
|
||||
XZW7 M -1;
|
||||
Stop;
|
||||
CrouchMove:
|
||||
#### # 4;
|
||||
XZW7 MN 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
|
||||
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
||||
XZW7 OPQRS 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
|
||||
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
||||
XZW7 TUV 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
|
||||
Goto CrouchMove+1;
|
||||
CrouchMoveRun:
|
||||
#### # 3;
|
||||
XZW7 MN 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
|
||||
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
||||
XZW7 OPQRS 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
|
||||
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
||||
XZW7 TUV 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
|
||||
Goto CrouchMoveRun+1;
|
||||
CrouchMoveFast:
|
||||
#### # 2;
|
||||
XZW7 MN 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
|
||||
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
|
||||
XZW7 OPQRS 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
|
||||
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
|
||||
XZW7 TUV 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
|
||||
Goto CrouchMoveFast+2;
|
||||
CrouchWave:
|
||||
#### # 3;
|
||||
XZWF ABCDEFGHIJKLMNO 3;
|
||||
Goto Crouch+1;
|
||||
CrouchApprove:
|
||||
#### # 3;
|
||||
XZWF PQRST 3;
|
||||
XZWF U 3 { facegrin = true; }
|
||||
XZWF VWXYZ 3;
|
||||
XZWG ABCDEF 3;
|
||||
Goto Crouch+1;
|
||||
CrouchVictory:
|
||||
#### # 3;
|
||||
XZWG GHIJ 3;
|
||||
XZWG K 3 { facegrin = true; }
|
||||
XZWG LMNOPQRSTUVWXY 3;
|
||||
Goto Crouch+1;
|
||||
CrouchBlowKiss:
|
||||
#### # 3;
|
||||
XZWG Z 3;
|
||||
XZWH ABC 3;
|
||||
XZWH D 3 { faceblink = true; }
|
||||
XZWH EFGHIJ 3;
|
||||
XZWH K 3 { facewink = true; }
|
||||
XZWH LMNOPQR 3;
|
||||
Goto Crouch+1;
|
||||
CrouchMissile:
|
||||
XZW7 M 2;
|
||||
XZW7 WXYZ 2;
|
||||
XZW8 AB 2;
|
||||
Goto Crouch+1;
|
||||
CrouchMelee:
|
||||
XZW7 M 2;
|
||||
XZWA Z 2;
|
||||
XZWB ABCDEFGHIJKL 2;
|
||||
Goto Crouch+1;
|
||||
CrouchFastMelee:
|
||||
XZW7 M 2;
|
||||
XZWA Z 1;
|
||||
XZWB ABCDEFGHIJK 1;
|
||||
XZWB L 2;
|
||||
Goto Crouch+1;
|
||||
CrouchReload:
|
||||
XZW7 M 2;
|
||||
XZWB MNOPQRSTUVWXYZ 2;
|
||||
XZWC ABCDEFGHIJ 2;
|
||||
Goto Crouch+1;
|
||||
CrouchFastReload:
|
||||
XZW7 M 2;
|
||||
XZWB MNOPQRSTUVWXYZ 1;
|
||||
XZWC ABCDEFGHIJ 1;
|
||||
Goto Crouch+1;
|
||||
CrouchCheckGun:
|
||||
XZW7 M 2;
|
||||
XZWC LMNOPQRSTUVWXYZ 2;
|
||||
XZWD ABCD 2;
|
||||
Goto Crouch+1;
|
||||
CrouchPain:
|
||||
XZW7 M 1;
|
||||
XZW8 C 1 A_DemoPain();
|
||||
XZW8 DEF 1;
|
||||
Goto Crouch+1;
|
||||
CrouchDeath:
|
||||
XZW7 M 2
|
||||
{
|
||||
A_DemoScream();
|
||||
bSOLID = true; // we need this to avoid clipping into things
|
||||
}
|
||||
XZW8 GHIJK 2;
|
||||
XZW8 L 1 A_DMFade();
|
||||
Wait;
|
||||
EndCrouch:
|
||||
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
|
||||
XZW8 MNOPQRS 2;
|
||||
Goto Spawn+1;
|
||||
Float:
|
||||
#### # 3;
|
||||
XZWD XYZ 3;
|
||||
XZWE ABCDEFGHI 3;
|
||||
Goto Float+1;
|
||||
Swim:
|
||||
#### # 2;
|
||||
XZWE JKL 2;
|
||||
Goto SwimLoop;
|
||||
SwimLoop:
|
||||
#### # 5;
|
||||
XZWE MNO 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
|
||||
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
||||
XZWE PQRSTU 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
|
||||
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
||||
XZWE VWX 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
|
||||
Goto SwimLoop+1;
|
||||
SwimLoopRun:
|
||||
#### # 3;
|
||||
XZWE MNO 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
|
||||
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
||||
XZWE PQRSTU 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
|
||||
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
||||
XZWE VWX 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
|
||||
Goto SwimLoopRun+1;
|
||||
SwimLoopFast:
|
||||
#### # 2;
|
||||
XZWE MNO 2 A_FastJump("SwimLoop","SwimLoopRun",null);
|
||||
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
|
||||
XZWE PQRSTU 2 A_FastJump("SwimLoop","SwimLoopRun",null);
|
||||
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
|
||||
XZWE VWX 2 A_FastJump("SwimLoop","SwimLoopRun",null);
|
||||
Goto SwimLoopFast+1;
|
||||
SwimEnd:
|
||||
#### # 2;
|
||||
XZWE MYZ 2;
|
||||
Goto Float+1;
|
||||
}
|
||||
}
|
||||
359
zscript/player/swwm_player_cheats.zsc
Normal file
359
zscript/player/swwm_player_cheats.zsc
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
// the give/take cheat, oh boy
|
||||
|
||||
extend Class Demolitionist
|
||||
{
|
||||
void CheatGive_Health( int amount = 0 )
|
||||
{
|
||||
player.health = health = (amount>0)?(health+amount):default.health;
|
||||
}
|
||||
void CheatGive_Backpack()
|
||||
{
|
||||
let def = GetDefaultByType('HammerspaceEmbiggener');
|
||||
GiveInventory('TradedHammerspaceEmbiggener',def.MaxAmount,true);
|
||||
}
|
||||
void CheatGive_Ammo()
|
||||
{
|
||||
// Max out all mod ammo
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (class<Ammo>)(cls);
|
||||
if ( !type || type.IsAbstract() || (type.GetParentClass() != 'SWWMAmmo') ) continue;
|
||||
let ammoitem = Ammo(FindInventory(type));
|
||||
if ( !ammoitem )
|
||||
{
|
||||
// Add it first if not found
|
||||
ammoitem = Ammo(Spawn(type));
|
||||
ammoitem.Amount = 0;
|
||||
ammoitem.AttachToOwner(self);
|
||||
}
|
||||
// Don't give spares unless we own a Candy Gun, for consistency
|
||||
if ( (type is 'CandyGunSpares') && !FindInventory('CandyGun') ) continue;
|
||||
// Top up
|
||||
ammoitem.Amount = ammoitem.MaxAmount;
|
||||
// Does it have mag ammo?
|
||||
let sammoitem = SWWMAmmo(ammoitem);
|
||||
if ( !sammoitem || !sammoitem.MagAmmoType ) continue;
|
||||
let magitem = MagAmmo(FindInventory(sammoitem.MagAmmoType));
|
||||
if ( !magitem )
|
||||
{
|
||||
// Add it first if not found (shouldn't happen)
|
||||
magitem = MagAmmo(Spawn(sammoitem.MagAmmoType));
|
||||
magitem.AttachToOwner(self);
|
||||
}
|
||||
// Top up
|
||||
magitem.Amount = magitem.MaxAmount;
|
||||
}
|
||||
}
|
||||
void CheatGive_Armor()
|
||||
{
|
||||
// only give armors that have spares associated
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (Class<SWWMSpareArmor>)(cls);
|
||||
if ( !type || type.IsAbstract() || (type == 'SWWMSpareArmor') ) continue;
|
||||
if ( GetReplacement(type) != type ) continue;
|
||||
let def = GetDefaultByType(type);
|
||||
let armo = SWWMArmor(FindInventory(def.giveme));
|
||||
if ( !armo )
|
||||
{
|
||||
armo = SWWMArmor(Spawn(def.giveme));
|
||||
armo.AttachToOwner(self);
|
||||
}
|
||||
armo.Amount = armo.MaxAmount;
|
||||
}
|
||||
}
|
||||
void CheatGive_Keys()
|
||||
{
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (Class<Key>)(cls);
|
||||
if ( !type ) continue;
|
||||
let keyitem = GetDefaultByType(type);
|
||||
if ( !keyitem.special1 ) continue;
|
||||
let rep = GetReplacement(type); // handle replaced keys
|
||||
if ( !(rep is 'Key') ) continue;
|
||||
// iwad restrictions (vanilla doesn't care, but here they'll show in the inventory)
|
||||
if ( !(gameinfo.gametype&GAME_HERETIC) && ((rep is 'SWWMKeyGreen') || (rep is 'SWWMKeyBlue') || (rep is 'SWWMKeyYellow')) )
|
||||
continue;
|
||||
if ( !(gameinfo.gametype&GAME_DOOM) && ((rep is 'SWWMRedCard') || (rep is 'SWWMBlueCard') || (rep is 'SWWMYellowCard')) )
|
||||
continue;
|
||||
let item = Inventory(Spawn(rep));
|
||||
SWWMHandler.KeyTagFix(item);
|
||||
if ( item is 'SWWMKey' ) SWWMKey(item).propagated = true; // no anim
|
||||
key_reentrant = true;
|
||||
if ( !item.CallTryPickup(self) ) item.Destroy();
|
||||
key_reentrant = false;
|
||||
}
|
||||
}
|
||||
void CheatGive_Weapons()
|
||||
{
|
||||
let savedpending = player.PendingWeapon;
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (class<Weapon>)(cls);
|
||||
if ( !type || (type == "Weapon") ) continue;
|
||||
// Don't give already owned weapons
|
||||
let owned = FindInventory(type);
|
||||
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
|
||||
// Don't give replaced weapons unless the replacement was done by Dehacked.
|
||||
let rep = GetReplacement(type);
|
||||
if ( (rep == type) || (rep is "DehackedPickup") )
|
||||
{
|
||||
// Give the weapon only if it is set in a weapon slot.
|
||||
if ( !player.weapons.LocateWeapon(type) ) continue;
|
||||
readonly<Weapon> def = GetDefaultByType(type);
|
||||
if ( !def.bCheatNotWeapon && def.CanPickup(self) )
|
||||
GiveInventory(type,1,true);
|
||||
}
|
||||
}
|
||||
player.PendingWeapon = savedpending;
|
||||
}
|
||||
void CheatGive_Artifacts( int amount = 0 )
|
||||
{
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (class<Inventory>)(cls);
|
||||
if ( !type ) continue;
|
||||
let rep = GetReplacement(type);
|
||||
// don't give replaced items
|
||||
if ( rep != type ) continue;
|
||||
// no fabricators before hexen
|
||||
if ( !(gameinfo.gametype&GAME_HEXEN) && (type is 'AmmoFabricator') ) continue;
|
||||
// no barriers outside doom
|
||||
if ( !(gameinfo.gametype&GAME_DOOM) && (type is 'EBarrier') ) continue;
|
||||
// no gravity/tether outside raven
|
||||
if ( !(gameinfo.gametype&GAME_RAVEN) && ((type is 'GravitySuppressor') || (type is 'SafetyTether')) ) continue;
|
||||
// Don't give maxed items
|
||||
let owned = FindInventory(type);
|
||||
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
|
||||
let def = GetDefaultByType(type);
|
||||
// must have INVBAR and a valid icon (also can't be a puzzle item)
|
||||
if ( !def.bINVBAR || !def.ShouldSpawn() || !def.Icon.isValid() || (type is 'PuzzleItem') ) continue;
|
||||
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
|
||||
}
|
||||
}
|
||||
void CheatGive_PuzzlePieces( int amount = 0 )
|
||||
{
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (class<PuzzleItem>)(cls);
|
||||
if ( !type ) continue;
|
||||
let def = GetDefaultByType(type);
|
||||
if ( !def.Icon.isValid() ) continue;
|
||||
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
|
||||
}
|
||||
}
|
||||
void CheatGive_Collectibles()
|
||||
{
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
let type = (class<SWWMCollectible>)(cls);
|
||||
if ( !type || (type == 'SWWMCollectible') ) continue;
|
||||
let def = GetDefaultByType(type);
|
||||
// check that we can collect it in this IWAD
|
||||
if ( !def.ValidGame() ) continue;
|
||||
let item = Inventory(Spawn(cls));
|
||||
SWWMCollectible(item).propagated = true; // no score or anims
|
||||
if ( !item.CallTryPickup(self) ) item.Destroy();
|
||||
}
|
||||
}
|
||||
override void CheatGive( String name, int amount )
|
||||
{
|
||||
if ( !player.mo || (player.health <= 0) ) return;
|
||||
ingivecheat = true;
|
||||
int giveall = (name~=="everything")?ALL_YESYES:(name~=="all")?ALL_YES:ALL_NO;
|
||||
if ( giveall )
|
||||
{
|
||||
CheatGive_Health();
|
||||
CheatGive_Backpack();
|
||||
CheatGive_Armor();
|
||||
CheatGive_Keys();
|
||||
CheatGive_Weapons();
|
||||
CheatGive_Ammo();
|
||||
CheatGive_Artifacts();
|
||||
CheatGive_PuzzlePieces();
|
||||
if ( giveall == ALL_YESYES )
|
||||
CheatGive_Collectibles();
|
||||
}
|
||||
else if ( name ~== "health" ) CheatGive_Health(amount);
|
||||
else if ( name ~== "backpack" ) CheatGive_Backpack();
|
||||
else if ( name ~== "ammo" ) CheatGive_Ammo();
|
||||
else if ( name ~== "armor" ) CheatGive_Armor();
|
||||
else if ( name ~== "keys" ) CheatGive_Keys();
|
||||
else if ( name ~== "weapons" ) CheatGive_Weapons();
|
||||
else if ( name ~== "artifacts" ) CheatGive_Artifacts(amount);
|
||||
else if ( name ~== "puzzlepieces" ) CheatGive_PuzzlePieces(amount);
|
||||
else if ( name ~== "collectibles" ) CheatGive_Collectibles();
|
||||
else
|
||||
{
|
||||
Class<Inventory> type = name;
|
||||
if ( !type || type.IsAbstract() )
|
||||
{
|
||||
if ( CheckLocalView() )
|
||||
Console.Printf("'%s' is not a valid inventory item",name);
|
||||
ingivecheat = false;
|
||||
return;
|
||||
}
|
||||
GiveInventory(type,amount,true);
|
||||
}
|
||||
ingivecheat = false;
|
||||
}
|
||||
|
||||
void CheatTake_Health( int amount = 0 )
|
||||
{
|
||||
if ( amount >= health )
|
||||
{
|
||||
CheatSuicide();
|
||||
if ( player == players[consoleplayer] ) Console.HideConsole();
|
||||
return;
|
||||
}
|
||||
player.health = health -= amount;
|
||||
}
|
||||
void CheatTake_Backpack()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
if ( (i is 'HammerspaceEmbiggener') || (i is 'BackpackItem') )
|
||||
i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
void CheatTake_Ammo()
|
||||
{
|
||||
for ( Inventory i=inv; i; i=i.inv )
|
||||
{
|
||||
if ( (i is 'Ammo') || (i is 'MagAmmo') )
|
||||
i.Amount = 0;
|
||||
}
|
||||
}
|
||||
void CheatTake_Armor()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
// avoid unclearable (internal) or nodrain (powerup) armors
|
||||
if ( ((i is 'SWWMArmor') && !i.bUNCLEARABLE && !(SWWMArmor(i).bNODRAIN)) || (i is 'SWWMSpareArmor') )
|
||||
i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
void CheatTake_Keys()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
if ( i is 'Key' ) i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
void CheatTake_Weapons()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
// don't take away gestures
|
||||
if ( (i is 'Weapon') && !(i is 'SWWMGesture') && !(i is 'SWWMItemGesture') )
|
||||
i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
void CheatTake_Artifacts()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
if ( i.bINVBAR && i.Icon.isValid() && !(i is 'PuzzleItem') )
|
||||
i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
void CheatTake_PuzzlePieces()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
if ( i is 'PuzzleItem' ) i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
void CheatTake_Collectibles()
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
if ( i is 'SWWMCollectible' ) i.DepleteOrDestroy();
|
||||
i = next;
|
||||
}
|
||||
if ( mystats ) mystats.ownedcollectibles.Clear();
|
||||
}
|
||||
void CheatTake_Item( Class<Inventory> type, int amount = 0 )
|
||||
{
|
||||
Inventory i = inv;
|
||||
while ( i )
|
||||
{
|
||||
let next = i.inv;
|
||||
if ( i is type )
|
||||
{
|
||||
i.Amount -= max(amount,1);
|
||||
if ( i.Amount <= 0 )
|
||||
{
|
||||
if ( (i is 'SWWMCollectible') && mystats )
|
||||
{
|
||||
// remove from obtained list
|
||||
let idx = mystats.ownedcollectibles.Find(SWWMCollectible(i).GetClass());
|
||||
if ( idx < mystats.ownedcollectibles.Size() )
|
||||
mystats.ownedcollectibles.Delete(idx);
|
||||
}
|
||||
i.DepleteOrDestroy();
|
||||
}
|
||||
}
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
|
||||
override void CheatTake( String name, int amount )
|
||||
{
|
||||
if ( !player.mo || (player.health <= 0) ) return;
|
||||
int takeall = (name~=="everything")?ALL_YESYES:(name~=="all")?ALL_YES:ALL_NO;
|
||||
if ( takeall )
|
||||
{
|
||||
CheatTake_Ammo();
|
||||
CheatTake_Backpack();
|
||||
CheatTake_Armor();
|
||||
CheatTake_Keys();
|
||||
CheatTake_Weapons();
|
||||
CheatTake_Artifacts();
|
||||
CheatTake_PuzzlePieces();
|
||||
if ( takeall == ALL_YESYES )
|
||||
CheatTake_Collectibles();
|
||||
}
|
||||
else if ( name ~== "health" ) CheatTake_Health(amount);
|
||||
else if ( name ~== "backpack" ) CheatTake_Backpack();
|
||||
else if ( name ~== "ammo" ) CheatTake_Ammo();
|
||||
else if ( name ~== "armor" ) CheatTake_Armor();
|
||||
else if ( name ~== "keys" ) CheatTake_Keys();
|
||||
else if ( name ~== "weapons" ) CheatTake_Weapons();
|
||||
else if ( name ~== "artifacts" ) CheatTake_Artifacts();
|
||||
else if ( name ~== "puzzlepieces" ) CheatTake_PuzzlePieces();
|
||||
else if ( name ~== "collectibles" ) CheatTake_Collectibles();
|
||||
else
|
||||
{
|
||||
Class<Inventory> type = name;
|
||||
if ( !type || type.IsAbstract() )
|
||||
{
|
||||
if ( CheckLocalView() )
|
||||
Console.Printf("'%s' is not a valid inventory item",name);
|
||||
return;
|
||||
}
|
||||
CheatTake_Item(type,amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
zscript/player/swwm_player_extra.zsc
Normal file
82
zscript/player/swwm_player_extra.zsc
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// unique voodoo doll, for better compatibility
|
||||
Class SWWMVoodooDoll : PlayerPawn
|
||||
{
|
||||
Vector3 lastvel;
|
||||
|
||||
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
||||
{
|
||||
// as simple as it gets
|
||||
return "$OB_VOODOO";
|
||||
}
|
||||
override void Tick()
|
||||
{
|
||||
Super.Tick();
|
||||
// mikoportal compat
|
||||
if ( !(pos.z ~== -32768) ) return;
|
||||
if ( vel.length() <= lastvel.length() )
|
||||
vel = lastvel;
|
||||
lastvel = vel;
|
||||
}
|
||||
Default
|
||||
{
|
||||
Tag "$FN_VOODOO";
|
||||
Speed 1;
|
||||
Health 100;
|
||||
Radius 16;
|
||||
Height 56;
|
||||
Mass 100;
|
||||
PainChance 255;
|
||||
+NOBLOOD;
|
||||
+DONTGIB;
|
||||
+NOICEDEATH;
|
||||
+DONTCORPSE;
|
||||
+NOSKIN;
|
||||
-FRIENDLY;
|
||||
}
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
XZW1 A -1;
|
||||
Stop;
|
||||
Pain:
|
||||
XZW1 A 1;
|
||||
XZW1 B 2 A_StartSound("voodoodoll/hit",CHAN_BODY,CHANF_OVERLAP);
|
||||
XZW1 CDEF 2;
|
||||
Goto Spawn;
|
||||
Death:
|
||||
XZW1 A 2
|
||||
{
|
||||
A_StartSound("voodoodoll/fall",CHAN_BODY,CHANF_OVERLAP);
|
||||
A_NoBlocking();
|
||||
}
|
||||
XZW1 GHIJKLMNO 2;
|
||||
XZW1 PQR 2;
|
||||
XZW1 S -1;
|
||||
Stop;
|
||||
}
|
||||
}
|
||||
|
||||
// for the doom 2 cast
|
||||
Class CastDemolitionist : Actor
|
||||
{
|
||||
Default
|
||||
{
|
||||
DeathSound "demolitionist/death";
|
||||
}
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
See:
|
||||
ZYX1 ABCDEFGHIJKLMNOP 2;
|
||||
Loop;
|
||||
Missile:
|
||||
ZYX1 A 2;
|
||||
ZYX2 ABCDEF 2;
|
||||
Goto See;
|
||||
Death:
|
||||
ZYX1 A 2;
|
||||
ZYX3 ABCDEFGHIJKLMNOP 2;
|
||||
ZYX3 Q -1;
|
||||
Stop;
|
||||
}
|
||||
}
|
||||
161
zscript/player/swwm_player_face.zsc
Normal file
161
zscript/player/swwm_player_face.zsc
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// facial animation and cosmetics
|
||||
extend Class Demolitionist
|
||||
{
|
||||
private int GetRandom()
|
||||
{
|
||||
return (rss = (rss<<1)*35447+(rss/87));
|
||||
}
|
||||
|
||||
private void UpdateTags()
|
||||
{
|
||||
if ( !tagcolor ) tagcolor = CVar.GetCVar('swwm_tagcolor',player);
|
||||
static const String colname[] =
|
||||
{
|
||||
"",
|
||||
"Blue",
|
||||
"Cyan",
|
||||
"Dragonfly",
|
||||
"Gold",
|
||||
"Magenta",
|
||||
"Orange",
|
||||
"Peach",
|
||||
"Pink",
|
||||
"Purple",
|
||||
"Red",
|
||||
"Violet",
|
||||
"White",
|
||||
"Yellow",
|
||||
"Black",
|
||||
"Rust"
|
||||
};
|
||||
int idx = tagcolor.GetInt();
|
||||
if ( (idx < 0) || (idx >= colname.Size()) ) idx = 0;
|
||||
if ( idx != oldtagcolor )
|
||||
A_ChangeModel("",0,"","",0,"models","DemoTags"..colname[idx]..".png",CMDL_USESURFACESKIN,-1);
|
||||
oldtagcolor = idx;
|
||||
for ( Inventory i=inv; i; i=i.inv )
|
||||
{
|
||||
if ( !(i is 'SWWMWeapon') ) continue;
|
||||
SWWMWeapon(i).UpdateTags(idx,colname[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFace()
|
||||
{
|
||||
// damage handling
|
||||
if ( facedamage )
|
||||
{
|
||||
if ( lastdamage > 70 )
|
||||
{
|
||||
facestate = FS_OUCH;
|
||||
facetimer = (lastdamagetimer-gametic)+10;
|
||||
}
|
||||
else if ( facestate < FS_OUCH )
|
||||
{
|
||||
facestate = FS_PAIN;
|
||||
facetimer = (lastdamagetimer-gametic)+10;
|
||||
paindir = 0;
|
||||
// paraphrased from vanilla, with some tweaks
|
||||
if ( player.attacker && (player.attacker != self) )
|
||||
{
|
||||
double atkang = AngleTo(player.attacker);
|
||||
double angdiff = deltaangle(angle,atkang);
|
||||
if ( abs(angdiff) < 135 )
|
||||
{
|
||||
if ( angdiff > 45 ) paindir = -1;
|
||||
else if ( angdiff < -45 ) paindir = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
facedamage = false;
|
||||
if ( facegrin && (facestate < FS_SAD) )
|
||||
{
|
||||
facestate = FS_GRIN;
|
||||
facetimer = 50;
|
||||
}
|
||||
facegrin = false;
|
||||
if ( facewink && (facestate < FS_SAD) )
|
||||
{
|
||||
facestate = FS_WINK;
|
||||
facetimer = 20;
|
||||
}
|
||||
facewink = false;
|
||||
if ( faceblink && (facestate < FS_PAIN) )
|
||||
{
|
||||
facestate = FS_BLINK;
|
||||
facetimer = 30;
|
||||
}
|
||||
faceblink = false;
|
||||
if ( facesad && (facestate <= FS_SAD) )
|
||||
{
|
||||
facestate = FS_SAD;
|
||||
facetimer = 50;
|
||||
}
|
||||
facesad = false;
|
||||
if ( FindInventory("RagekitPower") && (facestate < FS_PAIN) )
|
||||
{
|
||||
facestate = FS_EVIL;
|
||||
facetimer = 10;
|
||||
}
|
||||
if ( facetimer > 0 )
|
||||
{
|
||||
facetimer--;
|
||||
if ( facetimer <= 0 )
|
||||
{
|
||||
facestate = FS_DEFAULT;
|
||||
blinktime = 30;
|
||||
}
|
||||
}
|
||||
if ( !(gametic&1) )
|
||||
{
|
||||
if ( blinktime <= 0 )
|
||||
{
|
||||
blinktime--;
|
||||
if ( blinktime < -3 )
|
||||
{
|
||||
rss = MSTime();
|
||||
blinktime = (abs(GetRandom())%10)?(40+abs(GetRandom())%40):6;
|
||||
}
|
||||
}
|
||||
else blinktime--;
|
||||
}
|
||||
// update our face texture if different
|
||||
static const String facetex[] =
|
||||
{
|
||||
"Blank", "Blink", "Booty", "Dead",
|
||||
"Default", "Dizzy", "Evil", "Grin",
|
||||
"Hurt", "HurtLeft", "HurtRight",
|
||||
"Off", "Ouch", "Sad", "Smug",
|
||||
"Unamused", "Wink"
|
||||
};
|
||||
int faceidx = GetFaceTex();
|
||||
if ( !oldfaceidx || (faceidx != oldfaceidx) )
|
||||
A_ChangeModel("",0,"","",1,"models","DemoFace_"..facetex[faceidx]..".png",CMDL_USESURFACESKIN,-1);
|
||||
oldfaceidx = faceidx;
|
||||
}
|
||||
|
||||
private int GetFaceTex()
|
||||
{
|
||||
if ( player.Health <= 0 ) return 3;
|
||||
if ( (bInvulnerable || (player.cheats&(CF_GODMODE|CF_GODMODE2)) || FindInventory("InvinciballPower")) && (facestate >= FS_PAIN) ) return 14;
|
||||
if ( facestate == FS_OUCH ) return 12;
|
||||
if ( facestate == FS_PAIN ) return (paindir==1)?10:(paindir==-1)?9:8;
|
||||
if ( facestate == FS_GRIN ) return 7;
|
||||
if ( facestate == FS_EVIL ) return 6;
|
||||
if ( facestate == FS_SAD ) return 13;
|
||||
if ( facestate == FS_WINK ) return 16;
|
||||
if ( facestate == FS_BLINK ) return ((facetimer>28)||(facetimer<2))?15:1;
|
||||
switch ( blinktime )
|
||||
{
|
||||
case -1:
|
||||
case -3:
|
||||
return 15;
|
||||
break;
|
||||
case -2:
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
211
zscript/player/swwm_player_inventory.zsc
Normal file
211
zscript/player/swwm_player_inventory.zsc
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// inventory functions
|
||||
extend Class Demolitionist
|
||||
{
|
||||
override void GiveDefaultInventory()
|
||||
{
|
||||
if ( !player ) return;
|
||||
bInDefaultInventory = true; // prevent default inventory from adding lore entries (fixes lore library disappearing when loading saves WHILE DEAD)
|
||||
// we don't need hexen or basic armors
|
||||
// we can simplify the code a lot here since no handling of many special conditions is needed
|
||||
let sc = Inventory(Spawn('SayaCollar'));
|
||||
if ( !sc.CallTryPickup(self) ) sc.Destroy();
|
||||
let ap = Inventory(Spawn('AlmasteelPlating'));
|
||||
if ( !ap.CallTryPickup(self) ) ap.Destroy();
|
||||
let dp = Inventory(Spawn('DeepImpact'));
|
||||
if ( !dp.CallTryPickup(self) ) dp.Destroy();
|
||||
let eg = ExplodiumGun(Spawn('ExplodiumGun'));
|
||||
eg.firstselect = true;
|
||||
if ( !eg.CallTryPickup(self) ) eg.Destroy();
|
||||
else player.ReadyWeapon = player.PendingWeapon = eg;
|
||||
// in hexdd, we start with the chaos sphere in our grasp
|
||||
if ( SWWMUtility.IsDeathkings() && SWWMUtility.CheckMD5List("vanillahexen.list") )
|
||||
{
|
||||
let cs = Inventory(Spawn('SWWMChaosSphere'));
|
||||
if ( !cs.CallTryPickup(self) ) cs.Destroy();
|
||||
}
|
||||
// in deathmatch, we start with 8 embiggeners
|
||||
if ( deathmatch )
|
||||
{
|
||||
let em = Inventory(Spawn('TradedHammerspaceEmbiggener'));
|
||||
em.Amount = em.MaxAmount;
|
||||
if ( !em.CallTryPickup(self) ) em.Destroy();
|
||||
}
|
||||
bInDefaultInventory = false;
|
||||
}
|
||||
|
||||
override void ClearInventory()
|
||||
{
|
||||
Super.ClearInventory();
|
||||
// remove some specific "undroppable" items
|
||||
Actor last = self;
|
||||
while ( last.inv )
|
||||
{
|
||||
let i = last.inv;
|
||||
if ( ((i is 'SWWMArmor') && !i.bUNCLEARABLE) || (i is 'HammerspaceEmbiggener') )
|
||||
{
|
||||
i.DepleteOrDestroy();
|
||||
if ( !i.bDestroyed ) last = i;
|
||||
}
|
||||
else last = i;
|
||||
}
|
||||
}
|
||||
|
||||
override void RemoveInventory( Inventory item )
|
||||
{
|
||||
// stop any looping sounds our current weapon had
|
||||
if ( (item is 'SWWMWeapon') && player && (SWWMWeapon(item) == player.ReadyWeapon) )
|
||||
{
|
||||
A_StopSound(CHAN_WEAPONEXTRA);
|
||||
A_StopSound(CHAN_WEAPONEXTRA2);
|
||||
A_StopSound(CHAN_WEAPONEXTRA3);
|
||||
}
|
||||
Super.RemoveInventory(item);
|
||||
}
|
||||
|
||||
override void AddInventory( Inventory item )
|
||||
{
|
||||
Super.AddInventory(item);
|
||||
if ( !player ) return;
|
||||
String cn = item.GetClassName();
|
||||
if ( !bInDefaultInventory && (level.maptime > 0) && (!(item is 'SWWMWeapon') || !(cn.Left(4) ~== "Dual")) ) // dual weapons are ignored here, as they're handled separately
|
||||
{
|
||||
// add lore if any
|
||||
SWWMLoreLibrary.Add(player,cn);
|
||||
// weapon get oneliner
|
||||
if ( (item is 'Weapon') && !(item is 'SWWMGesture') && !(item is 'SWWMItemGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && (player == players[consoleplayer]) && !ingivecheat )
|
||||
{
|
||||
if ( (item is 'HeavyMahSheenGun') && !Random[DemoLines](0,2) && SWWMHandler.AddOneliner("sheenspecial",2,20) )
|
||||
{
|
||||
A_StartSound("sheen/specialpick",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
|
||||
A_StartSound("sheen/specialpick",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
|
||||
}
|
||||
else if ( (item is 'SWWMWeapon') && (SWWMWeapon(item).GetLine != "") )
|
||||
{
|
||||
// fall back to generic weapon get if voicepack lacks weapon-specific lines
|
||||
if ( !SWWMHandler.AddOneliner(SWWMWeapon(item).GetLine,2) )
|
||||
SWWMHandler.AddOneliner("getweapon",2);
|
||||
}
|
||||
else SWWMHandler.AddOneliner("getweapon",2);
|
||||
facegrin = true;
|
||||
}
|
||||
}
|
||||
if ( (item is 'Key') && !key_reentrant && !deathmatch && !bInDefaultInventory )
|
||||
{
|
||||
// score
|
||||
int score = 100;
|
||||
if ( player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),item.GetTag(),score);
|
||||
else Console.Printf(StringTable.Localize("$SWWM_FINDKEYREM"),player.GetUserName(),item.GetTag(),score);
|
||||
SWWMCredits.Give(player,score);
|
||||
SWWMScoreObj.Spawn(100,player.mo.Vec3Offset(0,0,Height/2));
|
||||
if ( !ingivecheat )
|
||||
{
|
||||
if ( !Random[DemoLines](0,5) &&
|
||||
((item.GetClass() == 'RedSkull') || (item.GetClass() == 'YellowSkull')
|
||||
|| (item.GetClass() == 'BlueSkull') || (item.GetClassName() == "PurpleSkull")) )
|
||||
{
|
||||
if ( !SWWMHandler.AddOneliner("skullget",2) )
|
||||
SWWMHandler.AddOneliner("keyget",2);
|
||||
}
|
||||
else SWWMHandler.AddOneliner("keyget",2);
|
||||
facegrin = true;
|
||||
}
|
||||
// share all keys in mp
|
||||
for ( int i=0; i<MAXPLAYERS; i++ )
|
||||
{
|
||||
if ( !playeringame[i] || !players[i].mo || (i == PlayerNumber()) )
|
||||
continue;
|
||||
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = true;
|
||||
let tkey = Inventory(Spawn(item.GetClass()));
|
||||
SWWMHandler.KeyTagFix(tkey);
|
||||
if ( !tkey.CallTryPickup(players[i].mo) ) tkey.Destroy();
|
||||
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = false;
|
||||
}
|
||||
}
|
||||
// add collectible to stats
|
||||
if ( item is 'SWWMCollectible' )
|
||||
{
|
||||
if ( !ingivecheat )
|
||||
{
|
||||
SWWMHandler.AddOneliner(SWWMCollectible(item).GetLine,2);
|
||||
facegrin = true;
|
||||
}
|
||||
if ( !mystats ) return;
|
||||
let cls = item.GetClass();
|
||||
if ( (mystats.ownedcollectibles.Size() > 0) && (mystats.ownedcollectibles.Find(cls) < mystats.ownedcollectibles.Size()) ) return;
|
||||
mystats.ownedcollectibles.Push(cls);
|
||||
}
|
||||
// yorick
|
||||
if ( (item is 'PuzzSkull') && mystats && !mystats.gotyorick && !ingivecheat )
|
||||
{
|
||||
mystats.gotyorick = true;
|
||||
SWWMHandler.AddOneliner("skullget",2);
|
||||
facegrin = true;
|
||||
}
|
||||
// notify key obtained to flash icon
|
||||
if ( item is 'Key' )
|
||||
EventHandler.SendInterfaceEvent(consoleplayer,"swwmkeyget."..item.GetClassName(),PlayerNumber());
|
||||
}
|
||||
|
||||
override bool UseInventory( Inventory item )
|
||||
{
|
||||
let itemtype = item.GetClass();
|
||||
if ( (player.cheats&CF_TOTALLYFROZEN) || isFrozen() ) return false;
|
||||
if ( !Actor.UseInventory(item) )
|
||||
{
|
||||
if ( player == players[consoleplayer] )
|
||||
{
|
||||
if ( !(item is 'Weapon') && !(item is 'SWWMCollectible') && !(item is 'Key') )
|
||||
A_StartSound("menu/noinvuse",CHAN_ITEMEXTRA);
|
||||
if ( item is 'PuzzleItem' )
|
||||
SWWMHandler.AddOneliner("puzzfail",2,20);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// use sounds of big powerups are heard by other players
|
||||
if ( (player == players[consoleplayer]) || item.bBIGPOWERUP )
|
||||
A_StartSound(item.UseSound,CHAN_ITEMEXTRA);
|
||||
if ( (player == players[consoleplayer]) && (item is 'PuzzleItem') )
|
||||
SWWMHandler.AddOneliner("puzzsucc",2,10);
|
||||
return true;
|
||||
}
|
||||
|
||||
// the default PickWeapon code breaks when dual weapons are involved
|
||||
// so we gotta roll out our own here
|
||||
//
|
||||
// I could rewrite PickNextWeapon and PickPrevWeapon as well, but nah
|
||||
// those always call CheckAmmo so it's fine
|
||||
private Weapon TraverseSlot( int slot, bool checkammo, int start, int end, Weapon cur )
|
||||
{
|
||||
for ( int i=start; i>=end; i-- )
|
||||
{
|
||||
let type = player.weapons.GetWeapon(slot,i);
|
||||
let w = Weapon(player.mo.FindInventory(type));
|
||||
if ( !w || (w == cur) || (cur && cur.bPOWERED_UP && (w == cur.SisterWeapon)) )
|
||||
continue;
|
||||
String sn = w.GetClassName();
|
||||
bool dual = ((type is 'SWWMWeapon') && (sn.Left(4) ~== "Dual"));
|
||||
if ( (!checkammo && (!dual || (w.SisterWeapon && (w.SisterWeapon.Amount > 1))))
|
||||
|| w.CheckAmmo(Weapon.EitherFire,false) )
|
||||
return w;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
override Weapon PickWeapon( int slot, bool checkammo )
|
||||
{
|
||||
int slotsize = player.weapons.SlotSize(slot);
|
||||
let cur = player.ReadyWeapon;
|
||||
bool found = false;
|
||||
int cs, ci;
|
||||
if ( cur ) [found, cs, ci] = player.weapons.LocateWeapon(cur.GetClass());
|
||||
if ( found && (cs == slot) )
|
||||
{
|
||||
// traverse the slot down from current index
|
||||
let w = TraverseSlot(slot,checkammo,ci-1,0,cur);
|
||||
if ( !w ) w = TraverseSlot(slot,checkammo,slotsize-1,ci+1,cur);
|
||||
return w?w:cur;
|
||||
}
|
||||
let w = TraverseSlot(slot,checkammo,slotsize-1,0,cur);
|
||||
return w?w:cur;
|
||||
}
|
||||
}
|
||||
640
zscript/player/swwm_player_move.zsc
Normal file
640
zscript/player/swwm_player_move.zsc
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
// general movement code
|
||||
extend Class Demolitionist
|
||||
{
|
||||
// directional movement without straferunning
|
||||
Vector2 NormalizedMove()
|
||||
{
|
||||
if ( !(player.cmd.forwardmove|player.cmd.sidemove) )
|
||||
return (0,0);
|
||||
int idx = !!(player.cmd.buttons&BT_RUN);
|
||||
// ratio between forwardmove and sidemove (depending on BT_RUN state)
|
||||
double fs = gameinfo.normforwardmove[idx]/gameinfo.normsidemove[idx];
|
||||
// raw axes scaled to 1:1 ratio
|
||||
Vector2 mvec = (player.cmd.forwardmove,-player.cmd.sidemove*fs);
|
||||
// multiply unit vector back to "raw" running speed (as TweakSpeed handles the "true" scaling for us later)
|
||||
return mvec.unit()*gameinfo.normforwardmove[1]*256.;
|
||||
}
|
||||
|
||||
double TweakSpeed()
|
||||
{
|
||||
double fact = bWalking?.08:(player.cmd.buttons&BT_RUN)?1.1:.4;
|
||||
for ( Inventory i=Inv; i; i=i.Inv ) fact *= i.GetSpeedFactor();
|
||||
return fact;
|
||||
}
|
||||
|
||||
override void CalcHeight()
|
||||
{
|
||||
double defviewh = viewheight+player.crouchviewdelta;
|
||||
oldbob = player.bob;
|
||||
if ( bFlyCheat || (player.cheats&CF_NOCLIP2) )
|
||||
player.bob = 0.;
|
||||
else
|
||||
player.bob = min((player.vel dot player.vel)*player.GetMoveBob(),MAXBOB);
|
||||
if ( player.cheats&CF_NOVELOCITY )
|
||||
{
|
||||
player.viewz = pos.z+defviewh;
|
||||
if ( player.viewz > ceilingz-4 )
|
||||
player.viewz = ceilingz-4;
|
||||
SetViewPos((0.,0.,0.));
|
||||
return;
|
||||
}
|
||||
// adjust viewheight
|
||||
if ( player.playerstate == PST_LIVE )
|
||||
{
|
||||
player.viewheight += player.deltaviewheight;
|
||||
if ( player.viewheight > defviewh )
|
||||
{
|
||||
player.viewheight = defviewh;
|
||||
player.deltaviewheight = 0.;
|
||||
}
|
||||
else if ( player.viewheight < (defviewh/2.) )
|
||||
{
|
||||
player.viewheight = defviewh/2.;
|
||||
if ( player.deltaviewheight <= 0. )
|
||||
player.deltaviewheight = 1./65536.;
|
||||
}
|
||||
if ( player.deltaviewheight )
|
||||
{
|
||||
player.deltaviewheight += .25;
|
||||
if ( !player.deltaviewheight )
|
||||
player.deltaviewheight = 1./65536.;
|
||||
}
|
||||
}
|
||||
// apply bobbing (formula adapted from Unreal)
|
||||
oldbobtime = bobtime;
|
||||
double vel2d = vel.xy.length();
|
||||
if ( vel2d < .25 ) bobtime += .2/GameTicRate;
|
||||
else bobtime += (.5+.8*min(vel2d/10.,3.))/GameTicRate;
|
||||
Vector2 bob = (sin(bobtime*180.),sin(bobtime*360.))*player.bob*.25;
|
||||
if ( player.morphtics )
|
||||
bob = (0.,0.);
|
||||
// set up viewz
|
||||
player.viewz = pos.z+player.viewheight+(bob.y*clamp(viewbob,0.,1.5));
|
||||
// handle smooth step down (hacky but looks ok)
|
||||
player.viewz += ssup;
|
||||
ssup = max(0,(ssup*.7)-.25);
|
||||
if ( floorclip && (player.playerstate != PST_DEAD) && (pos.z <= floorz) )
|
||||
player.viewz -= floorclip;
|
||||
if ( player.viewz > ceilingz-4 )
|
||||
player.viewz = ceilingz-4;
|
||||
if ( player.viewz < floorz+4 )
|
||||
player.viewz = floorz+4;
|
||||
// add viewpos Y for side bob
|
||||
SetViewPos((0.,bob.x*clamp(viewbob,0.,1.5),0.));
|
||||
}
|
||||
|
||||
override void CheckPitch()
|
||||
{
|
||||
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
|
||||
return; // handled in moveplayer
|
||||
Super.CheckPitch();
|
||||
}
|
||||
|
||||
override void CheckMoveUpDown()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash")) )
|
||||
player.cmd.upmove = 0;
|
||||
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
|
||||
{
|
||||
double fs = TweakSpeed();
|
||||
Vector3 x, y, z;
|
||||
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
|
||||
Vector3 accel;
|
||||
if ( (player.cmd.upmove == -32768) || sendtoground )
|
||||
{
|
||||
sendtoground = true;
|
||||
player.centering = true;
|
||||
accel = (0,0,-4096);
|
||||
}
|
||||
else accel = z*player.cmd.upmove*8.;
|
||||
accel *= fs/128.;
|
||||
vel = vel+accel/GameTicRate;
|
||||
if ( sendtoground ) vel.xy *= .6;
|
||||
if ( (pos.z <= floorz) || bOnMobj ) sendtoground = false;
|
||||
if ( vel.length() > 50. )
|
||||
vel = vel.unit()*50.;
|
||||
return;
|
||||
}
|
||||
else sendtoground = false;
|
||||
Super.CheckMoveUpDown();
|
||||
}
|
||||
|
||||
private bool ShouldDecelerate( Sector s )
|
||||
{
|
||||
// check if we can apply fast decel while standing on this sector
|
||||
// (important to not break certain intended vanilla effects)
|
||||
if ( bNOFRICTION )
|
||||
return false; // we don't have friction at all (e.g.: while dashing)
|
||||
if ( bWINDTHRUST && (s.special >= 40) && (s.special <= 51) )
|
||||
return false; // wind
|
||||
if ( (s.special == 84) || (s.special == 118) || (s.special >= 201) && (s.special <= 244) )
|
||||
return false; // current
|
||||
return true;
|
||||
}
|
||||
|
||||
override void MovePlayer()
|
||||
{
|
||||
if ( !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
|
||||
{
|
||||
dashboost = 0.;
|
||||
player.vel *= 0.;
|
||||
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
|
||||
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
|
||||
Super.MovePlayer();
|
||||
return;
|
||||
}
|
||||
bool isdashing = InStateSequence(CurState,FindState("Dash"));
|
||||
if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0;
|
||||
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
|
||||
{
|
||||
player.cheats &= ~CF_SCALEDNOLERP; // we cannot permit this flag here
|
||||
player.onground = false;
|
||||
if ( player.turnticks )
|
||||
{
|
||||
player.turnticks--;
|
||||
angle += (180./TURN180_TICKS);
|
||||
}
|
||||
else guideangle += .2*player.cmd.yaw*(360./65536.);
|
||||
guidepitch -= .2*player.cmd.pitch*(360./65536.);
|
||||
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
|
||||
guideroll = clamp(deltaangle(roll,0),-3.,3.);
|
||||
A_SetAngle(angle+guideangle,SPF_INTERPOLATE);
|
||||
A_SetPitch(clamp(pitch+guidepitch,player.MinPitch,player.MaxPitch),SPF_INTERPOLATE);
|
||||
A_SetRoll(roll+guideroll,SPF_INTERPOLATE);
|
||||
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
|
||||
{
|
||||
guideroll = guidepitch = roll = pitch = 0.;
|
||||
player.centering = false;
|
||||
}
|
||||
double fs = TweakSpeed();
|
||||
double jcmove = 0.;
|
||||
if ( player.cmd.buttons&BT_JUMP ) jcmove += 4096.;
|
||||
if ( player.cmd.buttons&BT_CROUCH ) jcmove -= 4096.;
|
||||
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
|
||||
Vector3 x, y, z;
|
||||
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
|
||||
Vector2 nmove = NormalizedMove();
|
||||
Vector3 accel = x*nmove.x-y*nmove.y+z*jcmove;
|
||||
accel *= fs/320.;
|
||||
double spd = vel.length();
|
||||
if ( spd > 40. ) vel = (vel+accel/GameTicRate).unit()*spd;
|
||||
else vel = vel+accel/GameTicRate;
|
||||
vel *= .95;
|
||||
player.vel = (1,1)*vel.length();
|
||||
player.jumptics = -2;
|
||||
if ( !(player.cheats & CF_PREDICTING) && (player.cmd.forwardmove|player.cmd.sidemove) )
|
||||
PlayRunning();
|
||||
if ( player.cheats&CF_REVERTPLEASE )
|
||||
{
|
||||
player.cheats &= ~CF_REVERTPLEASE;
|
||||
player.camera = player.mo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
player.cheats |= CF_SCALEDNOLERP; // smoother turning
|
||||
if ( player.turnticks )
|
||||
{
|
||||
player.turnticks--;
|
||||
angle += (180./TURN180_TICKS);
|
||||
}
|
||||
else angle += player.cmd.yaw*(360./65536.);
|
||||
player.onground = (pos.z<=floorz)||bOnMobj||bMBFBouncer||(player.cheats&CF_NOCLIP2);
|
||||
if ( player.cmd.forwardmove|player.cmd.sidemove )
|
||||
{
|
||||
double bobfactor;
|
||||
double friction, movefactor;
|
||||
[friction, movefactor] = GetFriction();
|
||||
bobfactor = (friction<ORIG_FRICTION)?movefactor:ORIG_FRICTION_FACTOR;
|
||||
if ( !player.onground && !bNoGravity )
|
||||
{
|
||||
// no air control here, done afterwards
|
||||
movefactor *= 0.;
|
||||
bobfactor *= 0.;
|
||||
}
|
||||
// use normalized movement vector, no SR40 (not that we need it with how fast we can run)
|
||||
Vector2 nmove = NormalizedMove();
|
||||
nmove *= TweakSpeed();
|
||||
nmove *= Speed/256.;
|
||||
// When crouching, speed and bobbing have to be reduced
|
||||
if ( CanCrouch() && (player.crouchfactor != 1) )
|
||||
{
|
||||
nmove *= player.crouchfactor;
|
||||
bobfactor *= player.crouchfactor;
|
||||
}
|
||||
nmove *= movefactor;
|
||||
if ( bNOGRAVITY && !player.GetClassicFlight() )
|
||||
{
|
||||
double zpush = nmove.x*sin(pitch);
|
||||
vel.z -= zpush;
|
||||
vel.xy += RotateVector(nmove,angle)*cos(pitch);
|
||||
player.vel += RotateVector(nmove,angle)*bobfactor*cos(pitch)*16.;
|
||||
}
|
||||
else
|
||||
{
|
||||
vel.xy += RotateVector(nmove,angle);
|
||||
if ( player.onground ) player.vel += RotateVector(nmove,angle)*bobfactor*16.;
|
||||
else player.vel *= .75;
|
||||
}
|
||||
// override air control because we REALLY need the extra mobility
|
||||
if ( !player.onground && !bNOGRAVITY )
|
||||
{
|
||||
nmove = NormalizedMove();
|
||||
double fs = TweakSpeed();
|
||||
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
|
||||
Vector2 accel = RotateVector(nmove,angle);
|
||||
accel *= fs/320.;
|
||||
double spd = vel.xy.length();
|
||||
double maxspd = fs*12.;
|
||||
if ( spd > maxspd ) vel.xy = (vel.xy+accel/GameTicRate).unit()*spd;
|
||||
else vel.xy = vel.xy+accel/GameTicRate;
|
||||
}
|
||||
if ( !(player.cheats&CF_PREDICTING) && (nmove.length() > 0.) )
|
||||
PlayRunning();
|
||||
if ( player.cheats&CF_REVERTPLEASE )
|
||||
{
|
||||
player.cheats &= ~CF_REVERTPLEASE;
|
||||
player.camera = player.mo;
|
||||
}
|
||||
}
|
||||
else if ( player.onground && ShouldDecelerate(floorsector) )
|
||||
{
|
||||
// quickly decelerate if we're not holding movement keys
|
||||
// (account for slippery floors, and assume approx .9 friction as "midpoint" for 85% reduction)
|
||||
double fact = clamp(floorsector.GetFriction(0)*.95,.5,1.);
|
||||
vel *= fact;
|
||||
player.vel *= fact;
|
||||
}
|
||||
if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE);
|
||||
}
|
||||
guideangle *= .9;
|
||||
guidepitch *= .9;
|
||||
guideroll *= .9;
|
||||
// anchor to ground when going down steps
|
||||
if ( lastground && !player.onground && !bFly && !bFlyCheat && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z <= 0) && !isdashing )
|
||||
{
|
||||
// test for gap crossing (i.e: climbing up platforms with holes between them)
|
||||
Vector3 storepos = pos;
|
||||
double storefloorz = floorz;
|
||||
bool crossgap = false;
|
||||
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
|
||||
{
|
||||
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),true);
|
||||
if ( floorz < storepos.z ) continue;
|
||||
crossgap = true;
|
||||
break;
|
||||
}
|
||||
SetOrigin(storepos,true);
|
||||
floorz = storefloorz;
|
||||
if ( !crossgap )
|
||||
{
|
||||
ssup = max(0,(pos.z-floorz));
|
||||
SetZ(floorz);
|
||||
lastground = player.onground = true;
|
||||
}
|
||||
}
|
||||
if ( player.onground ) lastgroundtic = level.maptime;
|
||||
else lastairtic = level.maptime;
|
||||
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
|
||||
PlayIdle();
|
||||
Vector3 dodge = (0,0,0), x, y, z;
|
||||
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
|
||||
int fm = player.cmd.forwardmove;
|
||||
int sm = player.cmd.sidemove;
|
||||
if ( !(fm|sm) ) fm = 1;
|
||||
if ( fm ) dodge += (fm>0)?X:-X;
|
||||
if ( sm ) dodge += (sm>0)?Y:-Y;
|
||||
if ( player.cmd.buttons&BT_CROUCH ) dodge = (0,0,-1); // death from above
|
||||
if ( dodge == (0,0,0) )
|
||||
{
|
||||
if ( !player.onground || bNOGRAVITY || (player.cheats&CF_NOCLIP2) ) dodge = X;
|
||||
else dodge.xy = RotateVector((1,0),angle);
|
||||
}
|
||||
if ( player.onground && !bNOGRAVITY && !(player.cheats&CF_NOCLIP2) )
|
||||
{
|
||||
dodge.z = max(0,dodge.z);
|
||||
if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z);
|
||||
}
|
||||
if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && (player.onground || level.IsJumpingAllowed() || (player.cmd.buttons&BT_CROUCH)) && (gamestate == GS_LEVEL) )
|
||||
{
|
||||
fullfuel = (dashfuel >= default.dashfuel);
|
||||
dashdir = dodge.unit();
|
||||
dashcooldown = 10;
|
||||
dashboost = 20.;
|
||||
bOnMobj = false;
|
||||
if ( player.cheats & CF_REVERTPLEASE )
|
||||
{
|
||||
player.cheats &= ~CF_REVERTPLEASE;
|
||||
player.camera = player.mo;
|
||||
}
|
||||
vel += dashdir*dashboost;
|
||||
vel.z += clamp(-vel.z*.4,0.,30.);
|
||||
player.jumptics = -1;
|
||||
SetStateLabel("Dash");
|
||||
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
|
||||
lastbump *= .95;
|
||||
mystats.dashcount++;
|
||||
BumpView(5.,vel);
|
||||
}
|
||||
}
|
||||
|
||||
override void CheckJump()
|
||||
{
|
||||
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
|
||||
if ( !(player.cmd.buttons&BT_JUMP) || (gamestate != GS_LEVEL) ) return;
|
||||
Vector2 walldir = AngleToVector(angle);
|
||||
bool walljump = false, wallclimb = false;
|
||||
double climbvelz = 0.;
|
||||
FLineTraceData d;
|
||||
Actor jumpactor = null;
|
||||
for ( int i=-4; i<int(height*.8); i++ )
|
||||
{
|
||||
if ( !walljump )
|
||||
{
|
||||
for ( int k=0; k<60; k+=10 )
|
||||
{
|
||||
for ( int j=-1; j<=1; j+=2 )
|
||||
{
|
||||
double ang = (angle-180)+k*j;
|
||||
walljump = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
|
||||
if ( walljump )
|
||||
{
|
||||
jumpactor = d.HitActor;
|
||||
walldir = (walldir*.7+AngleToVector(ang+180)*.3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( walljump ) break;
|
||||
}
|
||||
if ( walljump ) break;
|
||||
}
|
||||
if ( walljump ) break;
|
||||
if ( !wallclimb )
|
||||
{
|
||||
for ( int k=0; k<30; k+=10 )
|
||||
{
|
||||
for ( int j=-1; j<=1; j+=2 )
|
||||
{
|
||||
double ang = angle+k*j;
|
||||
wallclimb = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
|
||||
if ( wallclimb )
|
||||
{
|
||||
// double-check that it's climbable
|
||||
if ( d.HitType == TRACE_HitWall )
|
||||
{
|
||||
if ( !d.HitLine.sidedef[1] || !!(d.HitLine.flags&(Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING|Line.ML_BLOCK_PLAYERS)) ) // nope
|
||||
{
|
||||
wallclimb = false;
|
||||
break;
|
||||
}
|
||||
Sector s = d.HitLine.sidedef[!d.LineSide].sector;
|
||||
double backfloorz = s.floorplane.ZAtPoint(d.HitLocation.xy);
|
||||
double backceilz = s.ceilingplane.ZAtPoint(d.HitLocation.xy);
|
||||
// cling to 3D floor if hit
|
||||
if ( d.Hit3DFloor ) backfloorz = d.Hit3DFloor.top.ZAtPoint(d.HitLocation.xy);
|
||||
if ( ((backceilz-backfloorz) < Height) || ((pos.z+Height+32) < backfloorz) ) // nope
|
||||
{
|
||||
wallclimb = false;
|
||||
break;
|
||||
}
|
||||
climbvelz = 1.5*sqrt(max(1.,backfloorz-pos.z));
|
||||
}
|
||||
else if ( d.HitType == TRACE_HitActor )
|
||||
{
|
||||
if ( ((d.HitActor.ceilingz-d.HitActor.pos.z+d.HitActor.Height) < Height) || ((pos.z+Height+32) < (d.HitActor.pos.z+d.HitActor.Height)) ) // nope
|
||||
{
|
||||
wallclimb = false;
|
||||
break;
|
||||
}
|
||||
climbvelz = 1.5*sqrt(max(1.,(d.HitActor.pos.z+d.HitActor.Height)-pos.z));
|
||||
}
|
||||
jumpactor = d.HitActor;
|
||||
walldir = (walldir*.7+AngleToVector(ang)*.3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( wallclimb ) break;
|
||||
}
|
||||
if ( wallclimb ) break;
|
||||
}
|
||||
if ( wallclimb ) break;
|
||||
}
|
||||
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
|
||||
if ( level.maptime < (lastgroundtic+4) )
|
||||
{
|
||||
walljump = false;
|
||||
wallclimb = false;
|
||||
jumpactor = null;
|
||||
}
|
||||
if ( player.crouchoffset ) player.crouching = 1;
|
||||
else if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return;
|
||||
else if ( bNoGravity ) vel.z = 3;
|
||||
else if ( level.IsJumpingAllowed()
|
||||
&& ((player.onground && (player.jumptics == 0))
|
||||
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) )
|
||||
{
|
||||
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
|
||||
return;
|
||||
double jumpvelz = JumpZ*35./GameTicRate;
|
||||
double jumpfac = 0;
|
||||
for ( let p=Inv; p; p=p.Inv )
|
||||
{
|
||||
let pp = PowerHighJump(p);
|
||||
if ( pp )
|
||||
{
|
||||
double f = pp.Strength;
|
||||
if ( f > jumpfac ) jumpfac = f;
|
||||
}
|
||||
}
|
||||
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
|
||||
bool raging = FindInventory("RagekitPower");
|
||||
if ( raging ) jumpvelz *= 2.;
|
||||
double pvelz = vel.z;
|
||||
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
|
||||
{
|
||||
// check for wall stuff
|
||||
if ( walljump )
|
||||
{
|
||||
if ( vel.z < 10. )
|
||||
vel.z += 2.*jumpvelz+clamp(-pvelz*.6,0.,30.);
|
||||
vel.xy += walldir*20*Speed;
|
||||
lastbump *= .95;
|
||||
}
|
||||
else if ( wallclimb )
|
||||
{
|
||||
if ( vel.z < 10. )
|
||||
vel.z += climbvelz+clamp(-pvelz*.95,0.,30.);
|
||||
vel.xy += walldir*10*Speed;
|
||||
lastbump *= .97;
|
||||
}
|
||||
if ( walljump && jumpactor && jumpactor.bSHOOTABLE )
|
||||
{
|
||||
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
|
||||
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
|
||||
if ( raging )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",pos);
|
||||
ps.damagetype = 'Jump';
|
||||
ps.target = self;
|
||||
ps.special1 = dmg;
|
||||
}
|
||||
}
|
||||
if ( walljump || wallclimb )
|
||||
{
|
||||
last_kick = level.maptime+1;
|
||||
SWWMUtility.AchievementProgressInc("jump",1,player);
|
||||
}
|
||||
}
|
||||
bOnMobj = false;
|
||||
player.jumpTics = -1;
|
||||
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
|
||||
{
|
||||
dashboost = 3.;
|
||||
boostcooldown = 20;
|
||||
if ( vel.z < 10. )
|
||||
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
|
||||
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
|
||||
lastbump *= .95;
|
||||
mystats.boostcount++;
|
||||
last_boost = level.maptime+1;
|
||||
BumpView(3.,vel);
|
||||
SetStateLabel("Boost");
|
||||
}
|
||||
else
|
||||
{
|
||||
A_StartSound(walljump?"demolitionist/kick":"demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
||||
dashboost = 0.;
|
||||
// bunnyhop time
|
||||
if ( !walljump && !wallclimb )
|
||||
{
|
||||
if ( !bWalking && (level.maptime < (lastairtic+10)) )
|
||||
{
|
||||
SWWMUtility.AchievementProgressInc("bune",1,player);
|
||||
// bhop, z vel relative to vel size
|
||||
if ( vel.z < 25. ) // don't ramp up too hard
|
||||
{
|
||||
vel.z += jumpvelz*(((player.cmd.buttons&BT_RUN)?1.2:.65)+vel.length()*.01);
|
||||
// add part of last landing z velocity too
|
||||
vel.z += max(0,-landvelz*(raging?.45:.35));
|
||||
}
|
||||
// accelerate
|
||||
vel.xy += (RotateVector(NormalizedMove(),angle)/2400.)*(1.+vel.length()*.025)*TweakSpeed();
|
||||
}
|
||||
else
|
||||
{
|
||||
// first jump
|
||||
if ( vel.z < 10. ) // don't ramp up too hard
|
||||
{
|
||||
vel.z += jumpvelz*(bWalking?.75:(player.cmd.buttons&BT_RUN)?1.25:1.);
|
||||
// add part of last landing z velocity too
|
||||
vel.z += max(0,-landvelz*(raging?.35:.25));
|
||||
}
|
||||
// long jump if running/sprinting
|
||||
if ( !walljump && !wallclimb && !bWalking )
|
||||
vel.xy += (RotateVector(NormalizedMove(),angle)/1500.)*(raging?2.:1.)*TweakSpeed();
|
||||
}
|
||||
}
|
||||
BumpView(clamp((vel.length()-10)/12.,1.,20.),vel);
|
||||
if ( swwm_mutevoice < 4 )
|
||||
{
|
||||
int loudlv = swwm_voiceamp;
|
||||
A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
}
|
||||
SetStateLabel("Jump");
|
||||
}
|
||||
}
|
||||
last_jump_held = level.maptime+1;
|
||||
}
|
||||
|
||||
bool AllowCrouch()
|
||||
{
|
||||
if ( player.cmd.buttons&BT_JUMP ) return false;
|
||||
if ( InStateSequence(CurState,FindState("Dash")) ) return false; // no crouch during dash
|
||||
return true;
|
||||
}
|
||||
|
||||
// Imagine having to duplicate two functions only to change a couple values in both
|
||||
// I sure love constants
|
||||
override void CrouchMove( int direction )
|
||||
{
|
||||
double defaultheight = FullHeight;
|
||||
double savedheight = Height;
|
||||
double crouchspeed = direction*CROUCHSPEED*.8; // slow down slightly so it matches the animation
|
||||
double oldheight = player.viewheight;
|
||||
player.crouchdir = direction;
|
||||
player.crouchfactor += crouchspeed;
|
||||
// check whether the move is ok
|
||||
Height = defaultheight; // actually test the full height, or it'll look weird
|
||||
if ( !TryMove(pos.xy,false) )
|
||||
{
|
||||
Height = savedheight;
|
||||
if ( direction > 0 )
|
||||
{
|
||||
// doesn't fit
|
||||
player.crouchfactor -= crouchspeed;
|
||||
player.crouchdir = -1; // force crouch
|
||||
return;
|
||||
}
|
||||
}
|
||||
Height = savedheight;
|
||||
player.crouchfactor = clamp(player.crouchfactor,.3,1.);
|
||||
player.viewheight = ViewHeight*player.crouchfactor;
|
||||
player.crouchviewdelta = player.viewheight-ViewHeight;
|
||||
// Check for eyes going above/below fake floor due to crouching motion.
|
||||
CheckFakeFloorTriggers(pos.z+oldheight,true);
|
||||
}
|
||||
|
||||
override void CheckCrouch( bool totallyfrozen )
|
||||
{
|
||||
// crouch to swim/float down
|
||||
if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) && bNOGRAVITY )
|
||||
vel.z = -3;
|
||||
bool wascrouching = !!(player.cmd.buttons&BT_CROUCH);
|
||||
if ( !AllowCrouch() ) player.cmd.buttons &= ~BT_CROUCH;
|
||||
if ( CanCrouch() && (player.health > 0) && level.IsCrouchingAllowed() )
|
||||
{
|
||||
if ( !totallyfrozen )
|
||||
{
|
||||
int crouchdir = player.crouching;
|
||||
if ( !crouchdir ) crouchdir = (player.cmd.buttons&BT_CROUCH)?-1:1;
|
||||
else if ( player.cmd.buttons&BT_CROUCH ) player.crouching = 0;
|
||||
if ( (crouchdir == 1) && (player.crouchfactor < 1) && (pos.z+height < ceilingz) )
|
||||
CrouchMove(1);
|
||||
else if ( (crouchdir == -1) && (player.crouchfactor > .3) )
|
||||
CrouchMove(-1);
|
||||
}
|
||||
}
|
||||
else player.Uncrouch();
|
||||
player.crouchoffset = -(ViewHeight)*(1-player.crouchfactor);
|
||||
// we need the crouch button state to be preserved for other functions
|
||||
if ( wascrouching ) player.cmd.buttons |= BT_CROUCH;
|
||||
}
|
||||
|
||||
// let's customize our gravity
|
||||
override void FallAndSink( double grav, double oldfloorz )
|
||||
{
|
||||
if ( !player || (player.mo != self) || (player.cheats&CF_TOTALLYFROZEN) )
|
||||
{
|
||||
Super.FallAndSink(grav,oldfloorz);
|
||||
return;
|
||||
}
|
||||
// do nothing if standing on ground or "floating"
|
||||
if ( player.onground || bNOGRAVITY ) return;
|
||||
// ensure we don't pass terminal velocity just from falling
|
||||
if ( vel.z < -50 ) return;
|
||||
// we don't care about "the doom way" here, gravity is
|
||||
// ALWAYS in effect when not standing on solid ground
|
||||
if ( waterlevel > 1 )
|
||||
{
|
||||
// sink faster
|
||||
grav *= .35;
|
||||
}
|
||||
// reduce gravity while we're boosting
|
||||
else if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Boost")) )
|
||||
grav *= .25;
|
||||
vel.z -= grav;
|
||||
}
|
||||
}
|
||||
332
zscript/player/swwm_player_think.zsc
Normal file
332
zscript/player/swwm_player_think.zsc
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
// playerthink / deaththink
|
||||
extend Class Demolitionist
|
||||
{
|
||||
void CheckDefaceTexture()
|
||||
{
|
||||
if ( player.usedown )
|
||||
return;
|
||||
FLineTraceData d;
|
||||
LineTrace(angle,DEFMELEERANGE*2,pitch,TRF_THRUACTORS,player.viewheight,data:d);
|
||||
if ( d.HitType == TRACE_HitNone ) return;
|
||||
bool remove;
|
||||
TextureID replacewith;
|
||||
[remove, replacewith] = SWWMUtility.DefaceTexture(d.HitTexture);
|
||||
if ( !remove ) return;
|
||||
if ( (d.HitType != TRACE_HitWall) || !d.HitLine.special || !(d.HitLine.activation&SPAC_Use) )
|
||||
player.usedown = true;
|
||||
A_StartSound("bestsound",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
||||
lastbump *= .97;
|
||||
int scr = (TexMan.GetName(d.HitTexture).Left(6)~=="ZZWOLF")?200:20;
|
||||
if ( scr == 20 ) SWWMUtility.AchievementProgressInc("doodle",1,player);
|
||||
SWWMCredits.Give(player,scr);
|
||||
if ( player == players[consoleplayer] ) SWWMScoreObj.Spawn(scr,d.HitLocation);
|
||||
if ( d.HitType == TRACE_HitWall )
|
||||
{
|
||||
if ( d.Hit3DFloor )
|
||||
{
|
||||
// TODO connected textures for upper/lower
|
||||
if ( d.Hit3DFloor.flags&F3DFloor.FF_UPPERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(0,replacewith);
|
||||
else if ( d.Hit3DFloor.flags&F3DFloor.FF_LOWERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(2,replacewith);
|
||||
else d.Hit3DFloor.master.sidedef[0].SetTexture(1,replacewith);
|
||||
}
|
||||
else
|
||||
{
|
||||
// find connected sidedefs with the same texture
|
||||
Array<Line> con;
|
||||
con.Clear();
|
||||
con.Push(d.HitLine);
|
||||
Sector s = d.LineSide?d.HitLine.backsector:d.HitLine.frontsector;
|
||||
int found = 0;
|
||||
do
|
||||
{
|
||||
found = 0;
|
||||
foreach ( l:s.Lines )
|
||||
{
|
||||
if ( !l.sidedef[d.LineSide] || (l.sidedef[d.LineSide].GetTexture(d.LinePart) != d.HitTexture) )
|
||||
continue;
|
||||
if ( con.Find(l) < con.Size() ) continue;
|
||||
bool notmatched = true;
|
||||
foreach ( c:con )
|
||||
{
|
||||
if ( (l.v1 != c.v1) && (l.v2 != c.v2) && (l.v1 != c.v2) && (l.v2 != c.v1) )
|
||||
continue;
|
||||
notmatched = false;
|
||||
break;
|
||||
}
|
||||
if ( notmatched ) continue;
|
||||
con.Push(l);
|
||||
found++;
|
||||
}
|
||||
}
|
||||
while ( found > 0 );
|
||||
foreach ( c:con )
|
||||
c.sidedef[d.LineSide].SetTexture(d.LinePart,replacewith);
|
||||
}
|
||||
}
|
||||
else if ( d.HitType == TRACE_HitCeiling )
|
||||
{
|
||||
if ( d.Hit3DFloor )
|
||||
{
|
||||
if ( d.Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) d.Hit3DFloor.model.SetTexture(1,replacewith);
|
||||
else d.Hit3DFloor.model.SetTexture(0,replacewith);
|
||||
}
|
||||
else
|
||||
{
|
||||
// find connected sectors with the same ceiling texture (THIS IS VERY UGLY CODE)
|
||||
Array<Sector> con;
|
||||
con.Clear();
|
||||
con.Push(d.HitSector);
|
||||
int found;
|
||||
do
|
||||
{
|
||||
found = 0;
|
||||
foreach ( s:con )
|
||||
{
|
||||
foreach ( l:s.Lines )
|
||||
{
|
||||
// only check two-sided
|
||||
if ( !l.sidedef[1] ) continue;
|
||||
// don't check if there's a height difference
|
||||
if ( (l.frontsector.ceilingplane.ZAtPoint(l.v1.p) != l.backsector.ceilingplane.ZAtPoint(l.v1.p))
|
||||
|| (l.frontsector.ceilingplane.ZAtPoint(l.v2.p) != l.backsector.ceilingplane.ZAtPoint(l.v2.p)) )
|
||||
continue;
|
||||
if ( (l.frontsector.GetTexture(1) == d.HitTexture) && (con.Find(l.frontsector) >= con.Size()) )
|
||||
{
|
||||
found++;
|
||||
con.Push(l.frontsector);
|
||||
}
|
||||
if ( (l.backsector.GetTexture(1) == d.HitTexture) && (con.Find(l.backsector) >= con.Size()) )
|
||||
{
|
||||
found++;
|
||||
con.Push(l.backsector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while ( found > 0 );
|
||||
foreach ( s:con )
|
||||
s.SetTexture(1,replacewith);
|
||||
}
|
||||
}
|
||||
else if ( d.HitType == TRACE_HitFloor )
|
||||
{
|
||||
if ( d.Hit3DFloor )
|
||||
{
|
||||
if ( d.Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) d.Hit3DFloor.model.SetTexture(0,replacewith);
|
||||
else d.Hit3DFloor.model.SetTexture(1,replacewith);
|
||||
}
|
||||
else
|
||||
{
|
||||
// find connected sectors with the same floor texture (THIS IS VERY UGLY CODE)
|
||||
Array<Sector> con;
|
||||
con.Clear();
|
||||
con.Push(d.HitSector);
|
||||
int found;
|
||||
do
|
||||
{
|
||||
found = 0;
|
||||
foreach ( s:con )
|
||||
{
|
||||
foreach ( l:s.Lines )
|
||||
{
|
||||
// only check two-sided
|
||||
if ( !l.sidedef[1] ) continue;
|
||||
// don't check if there's a height difference
|
||||
if ( (l.frontsector.floorplane.ZAtPoint(l.v1.p) != l.backsector.floorplane.ZAtPoint(l.v1.p))
|
||||
|| (l.frontsector.floorplane.ZAtPoint(l.v2.p) != l.backsector.floorplane.ZAtPoint(l.v2.p)) )
|
||||
continue;
|
||||
if ( (l.frontsector.GetTexture(0) == d.HitTexture) && (con.Find(l.frontsector) >= con.Size()) )
|
||||
{
|
||||
found++;
|
||||
con.Push(l.frontsector);
|
||||
}
|
||||
if ( (l.backsector.GetTexture(0) == d.HitTexture) && (con.Find(l.backsector) >= con.Size()) )
|
||||
{
|
||||
found++;
|
||||
con.Push(l.backsector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while ( found > 0 );
|
||||
foreach ( s:con )
|
||||
s.SetTexture(0,replacewith);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckItemUsePickup()
|
||||
{
|
||||
if ( player.usedown )
|
||||
return;
|
||||
if ( !itrace ) itrace = new("SWWMItemTracer");
|
||||
Vector3 x, y, z, dir;
|
||||
[x, y, z] = SWWMUtility.GetPlayerAxes(self);
|
||||
Vector3 origin = SWWMUtility.GetPlayerEye(self);
|
||||
Sector os = level.PointInSector(origin.xy);
|
||||
int rings = 1;
|
||||
Array<Actor> ignoreme;
|
||||
ignoreme.Clear();
|
||||
for ( double i=0; i<.2; i+=.02 )
|
||||
{
|
||||
for ( int j=0; j<360; j+=(360/rings) )
|
||||
{
|
||||
dir = SWWMUtility.ConeSpread(x,y,z,j,i);
|
||||
itrace.Trace(origin,os,dir,UseRange,0);
|
||||
if ( itrace.Results.HitType != TRACE_HitActor ) continue;
|
||||
if ( ignoreme.Find(itrace.Results.HitActor) < ignoreme.Size() ) continue;
|
||||
player.usedown = true; // we found an item, ignore further uses
|
||||
if ( itrace.Results.HitActor.Used(self) ) return;
|
||||
ignoreme.Push(itrace.Results.HitActor);
|
||||
}
|
||||
rings += 2;
|
||||
}
|
||||
}
|
||||
|
||||
override void PlayerThink()
|
||||
{
|
||||
oldangle = angle;
|
||||
oldpitch = pitch;
|
||||
if ( player && (player.mo == self) && (player.playerstate != PST_DEAD) && (player.cmd.buttons&BT_USE) )
|
||||
{
|
||||
if ( !player.usedown ) lastuse = gametic;
|
||||
CheckDefaceTexture();
|
||||
if ( !player.usedown && froggy )
|
||||
player.usedown = froggy.Used(self);
|
||||
// try to "use" the item closest to the crosshair
|
||||
CheckItemUsePickup();
|
||||
}
|
||||
Super.PlayerThink();
|
||||
if ( (gametic == lastuse) && IsActorPlayingSound(CHAN_VOICE,"*usefail") )
|
||||
{
|
||||
failcounter++;
|
||||
if ( (failcounter > 8) && !Random[DemoLines](0,max(0,12-failcounter/3)) && (gametic > failcooldown) && (swwm_mutevoice < 2) )
|
||||
{
|
||||
failcooldown = SWWMHandler.AddOneliner("usefail",2,20);
|
||||
failcounter = max(4,failcounter-10);
|
||||
}
|
||||
else if ( (failcounter > 2) && Random[DemoLines](0,1) && (gametic > failcooldown) && (swwm_mutevoice < 4) )
|
||||
{
|
||||
int loudlv = swwm_voiceamp;
|
||||
A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
failcooldown = int(S_GetLength(String.Format("voice/%s/usegrunt",myvoice.GetString()))*GameTicRate);
|
||||
failcounter = max(2,failcounter-1);
|
||||
}
|
||||
}
|
||||
else if ( gametic > lastuse+50 ) failcounter = 0;
|
||||
oldlagangle = lagangle;
|
||||
oldlagpitch = lagpitch;
|
||||
lagangle = lagangle*.8+angle*.2;
|
||||
lagpitch = lagpitch*.8+pitch*.2;
|
||||
if ( !player || (player.mo != self) ) return;
|
||||
if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) )
|
||||
{
|
||||
// faster falloff
|
||||
player.jumptics -= 5;
|
||||
if ( player.onground && (player.jumptics < -18) )
|
||||
player.jumptics = 0;
|
||||
}
|
||||
if ( (player.playerstate != PST_DEAD) && !ReactionTime )
|
||||
{
|
||||
// quick grenade
|
||||
if ( player.cmd.buttons&BT_USER4 )
|
||||
SWWMGesture.SetGesture(self,GS_Grenade);
|
||||
// emergency melee with no weapon
|
||||
else if ( !player.ReadyWeapon && (player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_USER1)) )
|
||||
SWWMGesture.SetGesture(self,GS_EmptyMelee);
|
||||
}
|
||||
}
|
||||
|
||||
override void DeathThink()
|
||||
{
|
||||
player.Uncrouch();
|
||||
TickPSprites();
|
||||
player.onground = (pos.Z<=floorz);
|
||||
// ded (demo-chan falls faster tho)
|
||||
player.deltaviewheight = 0;
|
||||
if ( player.viewheight > 6 ) player.viewheight -= 3;
|
||||
if ( player.viewheight < 6 ) player.viewheight = 6;
|
||||
// center pitch
|
||||
double dpitch = clamp(deltaangle(pitch,0),-6,6);
|
||||
if ( abs(dpitch) < 3. ) pitch = 0.;
|
||||
else A_SetPitch(pitch+dpitch,SPF_INTERPOLATE);
|
||||
// add roll
|
||||
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
|
||||
if ( abs(droll) < 2. ) roll = 50.;
|
||||
else A_SetRoll(roll+droll,SPF_INTERPOLATE);
|
||||
player.mo.CalcHeight();
|
||||
if ( player.damagecount ) player.damagecount--;
|
||||
if ( player.poisoncount ) player.poisoncount--;
|
||||
// solid unless we can respawn, for safety
|
||||
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
|
||||
bSolid = false;
|
||||
else bSolid = true;
|
||||
if ( player.viewheight <= 6 )
|
||||
{
|
||||
deadtimer++;
|
||||
if ( (deadtimer == 60) && (player == players[consoleplayer]) )
|
||||
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI);
|
||||
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
|
||||
{
|
||||
// standard behaviour, respawn normally
|
||||
if ( (((player.cmd.buttons&BT_USE) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn)
|
||||
&& ((Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot)) )
|
||||
{
|
||||
player.cls = null;
|
||||
player.playerstate = PST_REBORN;
|
||||
if ( special1 > 2 ) special1 = 0;
|
||||
}
|
||||
}
|
||||
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) )
|
||||
{
|
||||
// reload save
|
||||
player.cls = null;
|
||||
player.playerstate = PST_ENTER;
|
||||
if ( special1 > 2 ) special1 = 0;
|
||||
}
|
||||
else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 120) && !FindInventory("SWWMReviveDisabler") && swwm_revive )
|
||||
{
|
||||
// reboot (if possible)
|
||||
if ( !FindInventory("ReviveCooldown") && (((swwm_revivecooldown >= 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4)) || !hasrevived) )
|
||||
{
|
||||
if ( hasrevived ) SWWMUtility.MarkAchievement("sekiro",player);
|
||||
hasrevived = true;
|
||||
player.Resurrect();
|
||||
player.damagecount = 0;
|
||||
player.bonuscount = 0;
|
||||
player.poisoncount = 0;
|
||||
blinktime = 30;
|
||||
SetState(FindState("Spawn")+1); // skip tweening
|
||||
roll = 0;
|
||||
let s = Spawn("DemolitionistShockwave",pos);
|
||||
s.target = self;
|
||||
s.special1 = 30;
|
||||
ReactionTime = 17;
|
||||
A_Stop();
|
||||
A_AlertMonsters(swwm_uncapalert?0:2500);
|
||||
if ( player == players[consoleplayer] )
|
||||
{
|
||||
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
||||
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
|
||||
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
|
||||
}
|
||||
lastbump *= 1.5;
|
||||
SWWMHandler.DoFlash(self,Color(255,255,255,255),10);
|
||||
SWWMHandler.DoFlash(self,Color(255,128,192,255),30);
|
||||
if ( special1 > 2 ) special1 = 0;
|
||||
if ( (swwm_revivecooldown > 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4) )
|
||||
GiveInventory("ReviveCooldown",1);
|
||||
}
|
||||
else if ( level.maptime > revivefail )
|
||||
{
|
||||
if ( player == players[consoleplayer] ) A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI);
|
||||
revivefail = level.maptime+120;
|
||||
}
|
||||
}
|
||||
}
|
||||
else deadtimer = 0;
|
||||
}
|
||||
}
|
||||
843
zscript/player/swwm_player_tick.zsc
Normal file
843
zscript/player/swwm_player_tick.zsc
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
// Tick and friends
|
||||
extend Class Demolitionist
|
||||
{
|
||||
void SenseItems()
|
||||
{
|
||||
if ( player.cmd.buttons&BT_USER3 )
|
||||
{
|
||||
let bt = BlockThingsIterator.Create(self,800);
|
||||
while ( bt.Next() )
|
||||
{
|
||||
let i = bt.Thing;
|
||||
if ( !i || (!(i is 'Inventory') && !(i is 'Chancebox') && !(i is 'SWWMRespawnTimer')) ) continue;
|
||||
if ( (i is 'Inventory') && (i.bINVISIBLE || !i.bSPECIAL || Inventory(i).Owner) ) continue;
|
||||
if ( (i is 'Chancebox') && (i.CurState != i.SpawnState) ) continue;
|
||||
if ( !SWWMUtility.SphereIntersect(i,pos,800) ) continue;
|
||||
if ( !level.allmap && !i.CheckSight(self,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
|
||||
SWWMItemSense.Spawn(self,i);
|
||||
}
|
||||
bt.Destroy();
|
||||
}
|
||||
SWWMItemsense itm = itemsense;
|
||||
SWWMItemsense prev = null, next;
|
||||
while ( itm )
|
||||
{
|
||||
next = itm.next;
|
||||
if ( itm.Tick() )
|
||||
{
|
||||
if ( prev ) prev.next = next;
|
||||
else itemsense = next;
|
||||
itm.Destroy();
|
||||
}
|
||||
else prev = itm;
|
||||
itm = next;
|
||||
}
|
||||
}
|
||||
|
||||
void CheckItemMagnet()
|
||||
{
|
||||
if ( magtime > 40 )
|
||||
{
|
||||
if ( !(player.cmd.buttons&BT_USE) || swwm_usetopickup )
|
||||
{
|
||||
magtime = 0;
|
||||
SWWMMagItem mi = magitem;
|
||||
while ( mi )
|
||||
{
|
||||
let next = mi.next;
|
||||
mi.Destroy();
|
||||
mi = next;
|
||||
}
|
||||
magitem_cnt = 0;
|
||||
return;
|
||||
}
|
||||
if ( (magitem_cnt < 8) && !swwm_usetopickup )
|
||||
{
|
||||
let bt = BlockThingsIterator.Create(self,500);
|
||||
while ( bt.Next() )
|
||||
{
|
||||
let t = bt.Thing;
|
||||
if ( !t || !(t is 'Inventory') || !t.bSPECIAL || !t.bDROPPED || t.bINVISIBLE || Inventory(t).Owner || !SWWMUtility.SphereIntersect(t,pos,500) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
|
||||
continue;
|
||||
let i = Inventory(t);
|
||||
Class<Inventory> cls = i.GetClass();
|
||||
if ( i is 'Ammo' ) cls = Ammo(i).GetParentAmmo();
|
||||
else if ( i is 'MaGammo' ) cls = MagAmmo(i).GetParentMagAmmo();
|
||||
let oi = FindInventory(cls);
|
||||
if ( !i.bALWAYSPICKUP && oi && (oi.Amount >= oi.MaxAmount) ) continue;
|
||||
if ( (i is 'SWWMWeapon') && SWWMWeapon(i).HasSwapWeapon(self) && swwm_swapweapons ) continue;
|
||||
if ( (i is 'SWWMDualWeaponGiver') && SWWMDualWeaponGiver(i).HasSwapWeapon(self) && swwm_swapweapons ) continue;
|
||||
bool addme = true;
|
||||
for ( SWWMMagItem mi=magitem; mi; mi=mi.next )
|
||||
{
|
||||
if ( mi.item != i ) continue;
|
||||
addme = false;
|
||||
break;
|
||||
}
|
||||
if ( !addme ) continue;
|
||||
let nmi = new("SWWMMagItem");
|
||||
nmi.target = self;
|
||||
nmi.item = i;
|
||||
nmi.next = magitem;
|
||||
i.A_StartSound("misc/magitem",CHAN_AMBEXTRA,CHANF_LOOP,.2,1.,.5);
|
||||
i.bNOGRAVITY = true;
|
||||
magitem = nmi;
|
||||
magitem_cnt++;
|
||||
}
|
||||
bt.Destroy();
|
||||
}
|
||||
SWWMMagItem itm = magitem;
|
||||
SWWMMagItem prev = null, next;
|
||||
while ( itm )
|
||||
{
|
||||
next = itm.next;
|
||||
if ( itm.Tick() )
|
||||
{
|
||||
if ( prev ) prev.next = next;
|
||||
else magitem = next;
|
||||
itm.Destroy();
|
||||
magitem_cnt--;
|
||||
}
|
||||
else prev = itm;
|
||||
itm = next;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( player.cmd.buttons&BT_USE ) magtime++;
|
||||
}
|
||||
|
||||
void CheckUnderwaterAmb( bool restore = false )
|
||||
{
|
||||
Vector3 headpos = Vec3Offset(0,0,player.viewheight);
|
||||
Vector3 centerpos = Vec3Offset(0,0,height/2);
|
||||
Sector headregion = null;
|
||||
if ( CurSector.moreflags&Sector.SECMF_UNDERWATER ) // check underwater sector
|
||||
headregion = CurSector;
|
||||
else if ( CurSector.heightsec && (Cursector.heightsec.moreflags&Sector.SECMF_UNDERWATERMASK) ) // check height transfer
|
||||
{
|
||||
let hsec = CurSector.heightsec;
|
||||
double fh = hsec.floorplane.ZAtPoint(pos.xy);
|
||||
if ( pos.z < fh )
|
||||
{
|
||||
if ( headpos.z <= fh )
|
||||
headregion = hsec;
|
||||
}
|
||||
else if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY) && (headpos.z > hsec.ceilingplane.ZAtPoint(pos.xy)) )
|
||||
headregion = hsec;
|
||||
}
|
||||
else // check 3D floors
|
||||
{
|
||||
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
let ff = CurSector.Get3DFloor(i);
|
||||
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || !(ff.flags&F3DFloor.FF_SWIMMABLE) ) continue;
|
||||
double ff_bottom = ff.bottom.ZAtPoint(pos.xy);
|
||||
double ff_top = ff.top.ZAtPoint(pos.xy);
|
||||
if ( (ff_top <= pos.z) || (ff_bottom > centerpos.z) ) continue;
|
||||
if ( headpos.z <= ff_top )
|
||||
headregion = ff.model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int curunder = UNDER_NONE;
|
||||
if ( headregion )
|
||||
{
|
||||
switch ( headregion.damagetype )
|
||||
{
|
||||
case 'Fire':
|
||||
case 'Lava':
|
||||
curunder = UNDER_LAVA;
|
||||
break;
|
||||
case 'Slime':
|
||||
case 'Poison':
|
||||
case 'PoisonCloud':
|
||||
curunder = UNDER_SLIME;
|
||||
break;
|
||||
case 'Ice':
|
||||
case 'Drowning':
|
||||
default:
|
||||
curunder = UNDER_WATER;
|
||||
break;
|
||||
}
|
||||
undercol = headregion.ColorMap.LightColor;
|
||||
}
|
||||
if ( (curunder != lastunder) || restore )
|
||||
{
|
||||
static const string undersnd[] = {"","misc/underwater","misc/underslime","misc/underlava"};
|
||||
static const string entersnd[] = {"","misc/waterenter","misc/slimeenter","misc/lavaenter"};
|
||||
static const string exitsnd[] = {"","misc/waterexit","misc/slimeexit","misc/lavaexit"};
|
||||
A_StopSound(CHAN_AMBEXTRA);
|
||||
if ( curunder > UNDER_NONE )
|
||||
{
|
||||
A_StartSound(undersnd[curunder],CHAN_AMBEXTRA,CHANF_LOOP|CHANF_UI);
|
||||
if ( !restore && (players[consoleplayer].Camera == self) )
|
||||
A_StartSound(entersnd[curunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
|
||||
}
|
||||
if ( !restore && (lastunder > UNDER_NONE) && (players[consoleplayer].Camera == self) )
|
||||
A_StartSound(exitsnd[lastunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
|
||||
}
|
||||
if ( curunder > UNDER_NONE )
|
||||
A_SoundVolume(CHAN_AMBEXTRA,(players[consoleplayer].Camera==self)?1.:0.);
|
||||
lastunder = curunder;
|
||||
}
|
||||
|
||||
override void Tick()
|
||||
{
|
||||
Vector3 oldpos = pos;
|
||||
// can't be poisoned
|
||||
PoisonDurationReceived = 0;
|
||||
PoisonPeriodReceived = 0;
|
||||
PoisonDamageReceived = 0;
|
||||
Super.Tick();
|
||||
if ( (gamestate != GS_LEVEL) || !player || (player.mo != self) || (freezetics > 0) ) return;
|
||||
UpdateFace();
|
||||
UpdateTags();
|
||||
if ( hasteleported )
|
||||
{
|
||||
// we just got teleported, don't count the travel distance
|
||||
oldpos = pos;
|
||||
hasteleported = false;
|
||||
}
|
||||
if ( !selflight )
|
||||
{
|
||||
selflight = new("DemolitionistSelfLight");
|
||||
selflight.ChangeStatNum(STAT_USER);
|
||||
selflight.target = self;
|
||||
selflight.Tick();
|
||||
}
|
||||
if ( !myshadow ) myshadow = SWWMShadow.Track(self);
|
||||
// double-check that we have these
|
||||
if ( !FindInventory("AlmasteelPlating") )
|
||||
{
|
||||
let ap = Inventory(Spawn("AlmasteelPlating"));
|
||||
if ( !ap.CallTryPickup(self) ) ap.Destroy();
|
||||
}
|
||||
if ( !FindInventory("SayaCollar") )
|
||||
{
|
||||
let sc = Inventory(Spawn("SayaCollar"));
|
||||
if ( !sc.CallTryPickup(self) ) sc.Destroy();
|
||||
}
|
||||
// this is why we need mod cvar callbacks
|
||||
if ( swwm_singlefirst != oldsinglefirst )
|
||||
WeaponSlots.SetupWeaponSlots(self);
|
||||
oldsinglefirst = swwm_singlefirst;
|
||||
// overheal fading
|
||||
if ( !isFrozen() && !(player.cheats&CF_TOTALLYFROZEN) )
|
||||
{
|
||||
if ( (health <= 200) || (health > oldhealth) )
|
||||
{
|
||||
healtimer = 0;
|
||||
healcooldown = 80;
|
||||
}
|
||||
else if ( health > 200 )
|
||||
{
|
||||
if ( healcooldown > 0 ) healcooldown--;
|
||||
else
|
||||
{
|
||||
if ( health > 1000 )
|
||||
{
|
||||
let spr = DivineSpriteEffect(FindInventory("DivineSpriteEffect"));
|
||||
if ( !spr || spr.bHealDone )
|
||||
A_SetHealth(max(1000,health-10));
|
||||
if ( health <= 1000 ) healcooldown = 40;
|
||||
}
|
||||
else if ( health > 500 )
|
||||
{
|
||||
if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%3) )
|
||||
A_SetHealth(health-1);
|
||||
if ( health <= 500 ) healcooldown = 20;
|
||||
}
|
||||
else if ( health > 200 )
|
||||
{
|
||||
if ( !FindInventory("RefresherRegen") && !(healtimer%12) )
|
||||
A_SetHealth(health-1);
|
||||
}
|
||||
healtimer++;
|
||||
}
|
||||
}
|
||||
}
|
||||
oldhealth = health;
|
||||
oldlagvel = lagvel;
|
||||
oldlagready = lagready;
|
||||
if ( player.weaponstate&WF_WEAPONBOBBING ) lagready = lagready*.9+.1;
|
||||
else lagready = lagready*.4;
|
||||
lagvel = lagvel*.8+vel*.2;
|
||||
double traveldist = level.Vec3Diff(oldpos,pos).length();
|
||||
if ( !player.onground || bNoGravity )
|
||||
{
|
||||
if ( waterlevel > 1 )
|
||||
{
|
||||
cairtime = 0;
|
||||
mystats.swimdist += traveldist;
|
||||
}
|
||||
else
|
||||
{
|
||||
cairtime++;
|
||||
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
|
||||
mystats.airdist += traveldist;
|
||||
}
|
||||
if ( (vel.z < -fallingscreamminspeed) && (vel.z > -fallingscreammaxspeed) && (player == players[consoleplayer]) )
|
||||
SWWMHandler.AddOneliner("falling",2,30);
|
||||
}
|
||||
else
|
||||
{
|
||||
SWWMHandler.CancelOneliner("falling");
|
||||
airscreamtime = 0;
|
||||
cairtime = 0;
|
||||
mystats.grounddist += traveldist;
|
||||
SWWMUtility.AchievementProgressIncDouble("travel",traveldist/32000.,player);
|
||||
}
|
||||
// spawn bubbles while underwater
|
||||
if ( (waterlevel > 1) && !Random[ExploS](0,5) )
|
||||
{
|
||||
int numpt = Random[ExploS](-2,2);
|
||||
for ( int i=0; i<numpt; i++ )
|
||||
{
|
||||
let p = Spawn("SWWMBubble",Vec3Offset(FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](height*.1,height*.9)));
|
||||
p.scale *= FRandom[ExploS](.02,.2);
|
||||
p.vel += vel*.2;
|
||||
}
|
||||
}
|
||||
CheckUnderwaterAmb();
|
||||
SenseItems();
|
||||
CheckItemMagnet();
|
||||
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
|
||||
if ( vel.length() > ((3600*GameTicRate)/32000.) )
|
||||
SWWMUtility.AchievementProgress("sanic",int((vel.length()*3600*GameTicRate)/32000.),player);
|
||||
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
||||
if ( player.onground && !bNoGravity && !lastground )
|
||||
{
|
||||
// bump down weapon
|
||||
bumpvelz -= lastvelz;
|
||||
BumpView(min(-lastvelz/10.,20));
|
||||
if ( lastvelz < -25 )
|
||||
{
|
||||
let s = Spawn("DemolitionistShockwave",pos);
|
||||
s.target = self;
|
||||
s.special1 = int(-lastvelz);
|
||||
A_AlertMonsters(swwm_uncapalert?0:2500);
|
||||
BumpView(15.);
|
||||
lastbump *= 1.1;
|
||||
if ( FindInventory("RagekitPower") )
|
||||
{
|
||||
// stop for just a split second UNLESS bunnyhopping
|
||||
if ( !(player.cmd.buttons&BT_RUN) || (level.maptime >= (lastairtic+10)) )
|
||||
ReactionTime = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
A_Stop();
|
||||
ReactionTime = 17;
|
||||
}
|
||||
if ( player == players[consoleplayer] )
|
||||
{
|
||||
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
||||
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
|
||||
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
|
||||
}
|
||||
mystats.stompcount++;
|
||||
}
|
||||
if ( lastvelz < -10 )
|
||||
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
||||
if ( (lastvelz < -gruntspeed) && (swwm_mutevoice < 4) && (health > 0) )
|
||||
{
|
||||
int loudlv = swwm_voiceamp;
|
||||
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
||||
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
|
||||
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
|
||||
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
|
||||
}
|
||||
if ( lastvelz < -1 )
|
||||
A_Footstep(0,1,clamp(-lastvelz*0.05,0.0,1.0),true);
|
||||
// bounce off slopes
|
||||
if ( pos.z <= floorz )
|
||||
{
|
||||
F3DFloor ff;
|
||||
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
||||
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
||||
ff = FloorSector.Get3DFloor(i);
|
||||
break;
|
||||
}
|
||||
Vector3 fnorm;
|
||||
if ( ff ) fnorm = -ff.top.Normal;
|
||||
else fnorm = FloorSector.floorplane.Normal;
|
||||
double maxsteep = clamp(abs(lastvelz)*.1,0.,.9);
|
||||
if ( fnorm.z < maxsteep ) vel = .8*fnorm*(-lastvelz)*(1.-.8*fnorm.z);
|
||||
}
|
||||
}
|
||||
// crush anything we're standing on
|
||||
bool dummy;
|
||||
Actor encroached;
|
||||
[dummy, encroached] = TestMobjZ();
|
||||
// add special check so corpses don't get stuck on top of monsters and players
|
||||
if ( encroached && encroached.bSOLID && (bSOLID || encroached.bACTLIKEBRIDGE) )
|
||||
{
|
||||
if ( (encroached is 'FroggyChair') && (encroached != oldencroached) )
|
||||
encroached.A_StartSound("squeak",CHAN_BODY,CHANF_OVERLAP);
|
||||
// try to follow movement (this method is awkward but works with monsters)
|
||||
if ( encroached == oldencroached )
|
||||
{
|
||||
Vector3 oldp = pos;
|
||||
Vector3 newp = level.Vec3Offset(pos,level.Vec3Diff(oldencroachedpos,encroached.pos));
|
||||
if ( level.IsPointInLevel(newp) )
|
||||
{
|
||||
SetOrigin(newp,true);
|
||||
if ( !TestMobjLocation() ) SetOrigin(oldp,true);
|
||||
}
|
||||
encroachtics++;
|
||||
if ( !(encroachtics%GameTicRate) && (encroached.bISMONSTER || encroached.player || (encroached is 'ScriptedMarine')) && !IsFriend(encroached) )
|
||||
SWWMUtility.AchievementProgress("step",encroachtics/GameTicRate,player);
|
||||
}
|
||||
else encroachtics = 0;
|
||||
oldencroached = encroached;
|
||||
oldencroachedpos = encroached.pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
// launch in movement direction (useful for moving platforms)
|
||||
// make sure we're not getting launched because an enemy just died under our feet, because that can cause some issues
|
||||
if ( oldencroached && (dashboost <= 0.) && (lastvelz >= -25) && (!oldencroached.bISMONSTER || (oldencroached.Health > 0)) ) vel += oldencroached.vel+level.Vec3Diff(oldencroachedpos,oldencroached.pos);
|
||||
oldencroached = null;
|
||||
encroachtics = 0;
|
||||
}
|
||||
if ( encroached && encroached.bSHOOTABLE && !encroached.bNODAMAGE && (lastvelz <= 0) && !(encroached is 'Demolitionist') )
|
||||
{
|
||||
if ( (lastvelz < -5) || !(level.maptime%5) )
|
||||
{
|
||||
int realdmg = encroached.DamageMobj(self,self,int((2+max(0,-lastvelz*3))*max(1.,mass/encroached.mass)),'Jump',DMG_THRUSTLESS);
|
||||
if ( FindInventory("RagekitPower") )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",pos);
|
||||
ps.damagetype = 'Jump';
|
||||
ps.target = self;
|
||||
ps.special1 = realdmg;
|
||||
}
|
||||
if ( (realdmg > 0) && encroached && !encroached.bNOBLOOD && !encroached.bINVULNERABLE )
|
||||
{
|
||||
encroached.TraceBleed(realdmg,self);
|
||||
encroached.SpawnBlood(pos,angle,realdmg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( abs(bumpvelz) > double.epsilon )
|
||||
{
|
||||
lagvel.z += bumpvelz*.2;
|
||||
bumpvelz *= .8;
|
||||
}
|
||||
if ( abs(bumpangle) > double.epsilon )
|
||||
{
|
||||
ViewAngle += bumpangle*.5;
|
||||
bumpangle *= .8;
|
||||
}
|
||||
if ( abs(bumppitch) > double.epsilon )
|
||||
{
|
||||
ViewPitch += bumppitch*.5;
|
||||
bumppitch *= .8;
|
||||
}
|
||||
if ( abs(bumproll) > double.epsilon )
|
||||
{
|
||||
ViewRoll += bumproll*.5;
|
||||
bumproll *= .8;
|
||||
}
|
||||
// stabilize view angles
|
||||
ViewAngle *= .8;
|
||||
ViewPitch *= .8;
|
||||
ViewRoll *= .8;
|
||||
player.cheats |= CF_INTERPVIEWANGLES;
|
||||
if ( player.onground && !lastground ) landvelz = lastvelz;
|
||||
else if ( !player.onground && lastground ) landvelz = 0;
|
||||
else if ( player.onground && lastground ) landvelz *= .9;
|
||||
lastground = player.onground;
|
||||
lastvelz = prevvelz;
|
||||
prevvelz = vel.z;
|
||||
bool isdashing = InStateSequence(CurState,FindState("Dash"));
|
||||
bool isboosting = InStateSequence(CurState,FindState("Boost"));
|
||||
bNOFRICTION = ((bFly&&!bFlyCheat&&!(player.cheats&CF_NOCLIP2))||isdashing);
|
||||
if ( fuelcooldown == 1 ) A_StartSound("demolitionist/fuelregen",CHAN_FUELREGEN,CHANF_LOOP,.35,4.,.5);
|
||||
else if ( fuelcooldown > 1 ) A_StopSound(CHAN_FUELREGEN);
|
||||
fuelcooldown = max(0,fuelcooldown-1);
|
||||
if ( dashlockst > 0 )
|
||||
{
|
||||
dashlockst--;
|
||||
if ( dashlockst == 0 ) A_StartSound("demolitionist/dashregen",CHAN_BODY,CHANF_OVERLAP,.5,4.);
|
||||
}
|
||||
else dashcooldown = max(0,dashcooldown-1);
|
||||
boostcooldown = max(0,boostcooldown-1);
|
||||
if ( (fuelcooldown <= 0) && (dashfuel < default.dashfuel) )
|
||||
{
|
||||
A_SoundPitch(CHAN_FUELREGEN,.5+1.5*((dashfuel/default.dashfuel)**2.));
|
||||
double oldfuel = dashfuel;
|
||||
dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.));
|
||||
// stops
|
||||
if ( (oldfuel < (default.dashfuel/24)) && (dashfuel >= default.dashfuel/24) )
|
||||
{
|
||||
dashfuel = default.dashfuel/24;
|
||||
fuelcooldown = 20;
|
||||
A_StartSound("demolitionist/fuelrgstp",CHAN_BODY,CHANF_OVERLAP,.6,4.,.6);
|
||||
}
|
||||
else if ( (oldfuel < (default.dashfuel/12)) && (dashfuel >= default.dashfuel/12) )
|
||||
{
|
||||
dashfuel = default.dashfuel/12;
|
||||
fuelcooldown = 10;
|
||||
A_StartSound("demolitionist/fuelrgstp",CHAN_BODY,CHANF_OVERLAP,.6,4.,.7);
|
||||
}
|
||||
if ( (oldfuel < dashfuel) && (dashfuel == default.dashfuel) )
|
||||
{
|
||||
A_StopSound(CHAN_FUELREGEN);
|
||||
A_StartSound("demolitionist/fuelrgend",CHAN_BODY,CHANF_OVERLAP,.6,4.);
|
||||
}
|
||||
}
|
||||
else if ( dashfuel >= default.dashfuel ) A_StopSound(CHAN_FUELREGEN);
|
||||
if ( ((dashboost <= 0) || !(isdashing || (isboosting && player.cmd.buttons&BT_JUMP))) && IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
|
||||
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
|
||||
PainChance = isdashing?0:255;
|
||||
if ( isdashing || (vel.length() > 30) )
|
||||
{
|
||||
bool oldpush = bCANPUSHWALLS;
|
||||
bool olduse = bCANUSEWALLS;
|
||||
bool oldmcross = bACTIVATEMCROSS;
|
||||
bool oldtele = bNOTELEPORT;
|
||||
// needed to prevent the many TryMove calls from activating unwanted lines
|
||||
bCANPUSHWALLS = false;
|
||||
bCANUSEWALLS = false;
|
||||
bACTIVATEMCROSS = false;
|
||||
bNOTELEPORT = true;
|
||||
Actor a;
|
||||
if ( isdashing && (dashboost > 0.) )
|
||||
{
|
||||
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
|
||||
{
|
||||
a = Spawn("DashTrail",Vec3Angle(15,angle+i*140,35));
|
||||
a.master = self;
|
||||
a.vel = (RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
|
||||
a.vel -= vel*.5;
|
||||
}
|
||||
}
|
||||
Vector3 dir = vel;
|
||||
double spd = dir.length();
|
||||
dir = dir/spd;
|
||||
Vector3 viewdir = SWWMUtility.Vec3FromAngles(angle,pitch);
|
||||
// look for things we could potentially bump into
|
||||
bool bumped = false;
|
||||
let bi = BlockThingsIterator.Create(self,500);
|
||||
let raging = RagekitPower(FindInventory("RagekitPower"));
|
||||
double maxmass = max(mass*spd/40.,200);
|
||||
if ( raging ) maxmass *= 2;
|
||||
while ( (spd > 0) && bi.Next() )
|
||||
{
|
||||
a = bi.Thing;
|
||||
if ( !a || (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS || a.bCORPSE || !CanCollideWith(a,false) || !a.CanCollideWith(self,true) ) continue;
|
||||
if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*(spd+radius),8) ) continue;
|
||||
if ( (a.pos.z <= a.floorz) && (a.height <= MaxStepHeight) ) continue;
|
||||
Vector3 diff = level.Vec3Diff(pos,a.pos);
|
||||
Vector3 dirto = diff.unit();
|
||||
if ( dir dot dirto < .1 ) continue;
|
||||
if ( (diff.z <= -a.height) && (lastvelz < -25) ) continue;
|
||||
if ( (diff.z <= -a.height) && !isdashing ) continue;
|
||||
// don't bump bridges if hit at a specific angle
|
||||
if ( a.bACTLIKEBRIDGE )
|
||||
{
|
||||
Vector3 bnorm = -dirto;
|
||||
if ( diff.z <= -a.height ) continue; // no bump from above
|
||||
else if ( diff.z >= Height ) bnorm = (0,0,-1);
|
||||
else if ( diff.x > a.Radius+Radius ) bnorm = (-1,0,0);
|
||||
else if ( diff.x < -(a.Radius+Radius) ) bnorm = (1,0,0);
|
||||
else if ( diff.y > a.Radius+Radius ) bnorm = (0,-1,0);
|
||||
else if ( diff.y < -(a.Radius+Radius) ) bnorm = (0,1,0);
|
||||
if ( dir dot bnorm > -.6 ) continue;
|
||||
}
|
||||
if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
|
||||
// large monsters will stop the player (unless hit from above if we're going at ground pound speed)
|
||||
A_QuakeEx(4,4,4,10,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
A_AlertMonsters(swwm_uncapalert?0:800);
|
||||
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
||||
a.A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
||||
bumptic = gametic+int(20+spd/4.);
|
||||
lastbump *= .8;
|
||||
if ( (a.bDONTTHRUST || a.bACTLIKEBRIDGE || (a.Mass >= maxmass) || (!a.bSHOOTABLE && !a.bPUSHABLE && (a.Health > 0))) && a.bSOLID && (dir dot dirto > .65) )
|
||||
{
|
||||
if ( bumped ) continue;
|
||||
bumped = true;
|
||||
SWWMUtility.AchievementProgressInc("bonk",1,player);
|
||||
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
vel *= .2;
|
||||
vel -= dir*(10+(spd*30/mass));
|
||||
vel -= dirto*(10+(spd*50/mass));
|
||||
vel.z += 5+(spd*(10/mass));
|
||||
dashboost *= 0.;
|
||||
}
|
||||
Vector3 pushdir = dirto*.1+dir*.9;
|
||||
if ( !a.bDONTTHRUST && (a.Mass < maxmass) && (a.bSHOOTABLE || a.bPUSHABLE) )
|
||||
{
|
||||
a.vel += pushdir*(25+(spd*20/max(50,a.mass)));
|
||||
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
|
||||
a.vel.z += 5+(spd*(5/max(50,a.mass)));
|
||||
}
|
||||
int flg = DMG_THRUSTLESS;
|
||||
if ( raging ) flg |= DMG_FOILINVUL;
|
||||
if ( !a.player && !a.bDONTBLAST ) a.bBLASTED = true;
|
||||
int dmg = int(10+spd*3.);
|
||||
bool buttslam = false;
|
||||
// BUTTSLAM
|
||||
if ( dir dot viewdir < -.3 )
|
||||
{
|
||||
dmg *= 3;
|
||||
buttslam = true;
|
||||
}
|
||||
if ( a.bSHOOTABLE )
|
||||
{
|
||||
dmg = a.DamageMobj(self,self,dmg,buttslam?'Buttslam':'Dash',flg);
|
||||
if ( (dmg > 0) && a && !a.bNOBLOOD && (raging || !a.bINVULNERABLE) )
|
||||
{
|
||||
a.TraceBleed(dmg,self);
|
||||
a.SpawnBlood(level.Vec3Offset(pos,diff/2),atan2(dir.y,dir.x)+180,dmg);
|
||||
}
|
||||
if ( buttslam && (!a || (a.Health <= 0)) )
|
||||
{
|
||||
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
|
||||
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
|
||||
Spawn("SWWMItemFog",level.Vec3Offset(pos,diff/2));
|
||||
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
|
||||
mystats.buttslams++;
|
||||
lastbump *= .8;
|
||||
}
|
||||
}
|
||||
if ( raging )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",level.Vec3Offset(pos,diff/2));
|
||||
ps.damagetype = buttslam?'Buttslam':'Dash';
|
||||
ps.target = self;
|
||||
ps.special1 = dmg;
|
||||
raging.DoHitFX();
|
||||
}
|
||||
}
|
||||
// check for ceiling collision
|
||||
if ( (spd > 0) && !bumped && ((pos.z+Height+dir.z*spd) >= ceilingz) )
|
||||
{
|
||||
F3DFloor ff;
|
||||
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
||||
ff = CeilingSector.Get3DFloor(i);
|
||||
break;
|
||||
}
|
||||
Vector3 ceilnorm;
|
||||
if ( ff ) ceilnorm = ff.model.floorplane.Normal;
|
||||
else ceilnorm = CeilingSector.ceilingplane.Normal;
|
||||
// don't bump if we're only grazing it
|
||||
if ( dir dot ceilnorm < -.6 )
|
||||
{
|
||||
bool busted = false;
|
||||
if ( raging || swwm_omnibust )
|
||||
{
|
||||
// see if we can bust it
|
||||
let tempme = new("LineTracer"); // gross hack to pass needed data
|
||||
int dmg = int(10+spd*3.);
|
||||
if ( raging ) dmg *= 8;
|
||||
bool buttslam = false;
|
||||
// BUTTSLAM
|
||||
if ( dir dot viewdir < -.3 )
|
||||
{
|
||||
dmg *= 3;
|
||||
buttslam = true;
|
||||
}
|
||||
if ( ff ) tempme.Results.ffloor = ff;
|
||||
tempme.Results.HitSector = CeilingSector;
|
||||
tempme.Results.HitType = TRACE_HitCeiling;
|
||||
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
|
||||
{
|
||||
// busted through
|
||||
if ( raging )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
|
||||
ps.damagetype = buttslam?'Buttslam':'Dash';
|
||||
ps.target = self;
|
||||
ps.special1 = int(10+spd*3.);
|
||||
raging.DoHitFX();
|
||||
}
|
||||
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
||||
busted = true;
|
||||
if ( buttslam )
|
||||
{
|
||||
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
|
||||
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
|
||||
Spawn("SWWMItemFog",Vec3Offset(0,0,Height));
|
||||
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
|
||||
mystats.buttslams++;
|
||||
lastbump *= .8;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !busted || !raging )
|
||||
{
|
||||
// headbump
|
||||
bumped = true;
|
||||
SWWMUtility.AchievementProgressInc("bonk",1,player);
|
||||
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
||||
bumptic = gametic+int(20+spd/4.);
|
||||
lastbump *= .8;
|
||||
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
A_AlertMonsters(swwm_uncapalert?0:800);
|
||||
vel *= .2;
|
||||
vel -= dir*(10+(spd*30/mass));
|
||||
vel.z += 5+(spd*(10/mass));
|
||||
vel += ceilnorm*(15+(spd*40/mass));
|
||||
dashboost *= 0.;
|
||||
if ( raging )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
|
||||
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
|
||||
ps.target = self;
|
||||
ps.special1 = int(10+spd*3.);
|
||||
raging.DoHitFX();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// check for wall collision
|
||||
FCheckPosition tm;
|
||||
Vector2 steppy = dir.xy*spd/8.;
|
||||
Vector3 oldpos = pos;
|
||||
bool oldthru = bTHRUACTORS;
|
||||
bTHRUACTORS = true;
|
||||
for ( int i=1; i<=8; i++ )
|
||||
{
|
||||
if ( (spd > 0) && !bumped && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) && SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKEVERYTHING|Line.ML_BLOCKING|Line.ML_BLOCK_PLAYERS) )
|
||||
{
|
||||
Vector3 wallnorm = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
||||
int lside = 1;
|
||||
if ( !BlockingLine.sidedef[1] || !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
|
||||
{
|
||||
lside = 0;
|
||||
wallnorm *= -1;
|
||||
}
|
||||
// don't bump if we're only grazing it
|
||||
if ( dir dot wallnorm > -.6 )
|
||||
continue;
|
||||
bool buttslam = false;
|
||||
// BUTTSLAM
|
||||
if ( dir dot viewdir < -.3 )
|
||||
{
|
||||
buttslam = true;
|
||||
// leave buttmark
|
||||
Vector3 vdir = (2,0,Height*.54);
|
||||
A_SprayDecal("ButtMark",172,SWWMUtility.RotateVector3(vdir,angle+90),dir);
|
||||
A_SprayDecal("ButtMark",172,SWWMUtility.RotateVector3(vdir,angle-90),dir);
|
||||
}
|
||||
if ( raging || swwm_omnibust )
|
||||
{
|
||||
// see if we can bust it
|
||||
let tempme = new("LineTracer"); // gross hack to pass needed data
|
||||
int dmg = int(10+spd*3.);
|
||||
if ( raging ) dmg *= 8;
|
||||
if ( buttslam ) dmg *= 3;
|
||||
tempme.Results.HitLine = BlockingLine;
|
||||
tempme.Results.HitType = TRACE_HitWall;
|
||||
tempme.Results.Side = lside;
|
||||
tempme.Results.Tier = TIER_MIDDLE;
|
||||
if ( BlockingLine.sidedef[1] )
|
||||
{
|
||||
double ceilz = BlockingLine.sidedef[!lside].sector.ceilingplane.ZAtPoint(pos.xy);
|
||||
double florz = BlockingLine.sidedef[!lside].sector.floorplane.ZAtPoint(pos.xy);
|
||||
if ( pos.z+Height >= ceilz )
|
||||
tempme.Results.Tier = TIER_UPPER;
|
||||
else if ( pos.z <= florz )
|
||||
tempme.Results.Tier = TIER_LOWER;
|
||||
}
|
||||
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
|
||||
{
|
||||
// busted through
|
||||
if ( raging )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
|
||||
ps.damagetype = buttslam?'Buttslam':'Dash';
|
||||
ps.target = self;
|
||||
ps.special1 = int(10+spd*3.);
|
||||
raging.DoHitFX();
|
||||
}
|
||||
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
||||
if ( buttslam )
|
||||
{
|
||||
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
|
||||
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
|
||||
Spawn("SWWMItemFog",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
|
||||
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
|
||||
mystats.buttslams++;
|
||||
lastbump *= .8;
|
||||
}
|
||||
if ( raging ) continue; // don't stop
|
||||
}
|
||||
}
|
||||
// wallbump
|
||||
bumped = true;
|
||||
SWWMUtility.AchievementProgressInc("bonk",1,player);
|
||||
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
||||
bumptic = gametic+int(25+spd/4.);
|
||||
lastbump *= .8;
|
||||
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
|
||||
A_AlertMonsters(swwm_uncapalert?0:800);
|
||||
vel *= .2;
|
||||
vel -= dir*(10+(spd*30/mass));
|
||||
vel += wallnorm*(10+(spd*50/mass));
|
||||
vel.z += 5+(spd*(10/mass));
|
||||
dashboost *= 0.;
|
||||
if ( raging )
|
||||
{
|
||||
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,Height/2.));
|
||||
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
|
||||
ps.target = self;
|
||||
ps.special1 = int(10+spd*3.);
|
||||
raging.DoHitFX();
|
||||
}
|
||||
// activate it
|
||||
int locknum = SWWMUtility.GetLineLock(BlockingLine);
|
||||
if ( !locknum || CheckKeys(locknum,false,true) )
|
||||
{
|
||||
hitactivate = true;
|
||||
BlockingLine.Activate(self,lside,SPAC_Use);
|
||||
hitactivate = false;
|
||||
}
|
||||
BlockingLine.Activate(self,lside,SPAC_Impact);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check for slope boosting (only if dashing)
|
||||
if ( (pos.z <= floorz) && (spd > 0) && isdashing )
|
||||
{
|
||||
F3DFloor ff;
|
||||
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
||||
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
||||
ff = FloorSector.Get3DFloor(i);
|
||||
break;
|
||||
}
|
||||
Vector3 fnorm;
|
||||
if ( ff ) fnorm = -ff.top.Normal;
|
||||
else fnorm = FloorSector.floorplane.Normal;
|
||||
// how far up should the boost be
|
||||
double xspd = spd*(1.-dir.z);
|
||||
vel.z += xspd*(1.-fnorm.z);
|
||||
}
|
||||
SetOrigin(oldpos,true);
|
||||
bTHRUACTORS = oldthru;
|
||||
bCANPUSHWALLS = oldpush;
|
||||
bCANUSEWALLS = olduse;
|
||||
bACTIVATEMCROSS = oldmcross;
|
||||
bNOTELEPORT = oldtele;
|
||||
}
|
||||
else if ( isboosting && (dashboost > 0.) )
|
||||
{
|
||||
Actor a;
|
||||
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
|
||||
{
|
||||
a = Spawn("DashTrail",Vec3Angle(10,angle+i*140,40));
|
||||
a.master = self;
|
||||
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
|
||||
a.vel -= vel*.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
124
zscript/utility/swwm_utility.zsc
Normal file
124
zscript/utility/swwm_utility.zsc
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Misc. Utility code
|
||||
|
||||
Class SWWMUtility
|
||||
{
|
||||
// gets the names of all mod cvars
|
||||
static clearscope void GetCVars( out Array<String> cvarlist )
|
||||
{
|
||||
cvarlist.Clear();
|
||||
let lmp = Wads.CheckNumForFullname("cvarinfo.base");
|
||||
if ( lmp == -1 ) ThrowAbortException("'cvarinfo.base' not found");
|
||||
String dat = Wads.ReadLump(lmp);
|
||||
Array<String> list, ln;
|
||||
// fucking Windows
|
||||
dat.Replace("\r","");
|
||||
list.Clear();
|
||||
dat.Split(list,"\n");
|
||||
foreach ( l:list )
|
||||
{
|
||||
if ( (l.Length() == 0) || (l.Left(2) == "//") || (l.Left(1) == "") )
|
||||
continue;
|
||||
int eq = l.IndexOf("=");
|
||||
if ( eq == -1 ) continue;
|
||||
l.Truncate(eq);
|
||||
ln.Clear();
|
||||
l.Split(ln," ",0);
|
||||
foreach ( w:ln )
|
||||
{
|
||||
if ( (w.Left(5) != "swwm_") ) continue;
|
||||
cvarlist.Push(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sends
|
||||
static clearscope void SendTooltip( Class<SWWMWeapon> which )
|
||||
{
|
||||
if ( !swwm_weapontooltips ) return;
|
||||
CVar v = CVar.FindCVar('swwm_tooltipshown');
|
||||
String tt = v.GetString();
|
||||
Array<String> wpn;
|
||||
tt.Split(wpn,",");
|
||||
foreach ( w:wpn )
|
||||
{
|
||||
if ( w == which.GetClassName() ) return;
|
||||
}
|
||||
if ( tt == "" ) tt = which.GetClassName();
|
||||
else tt = tt..","..which.GetClassName();
|
||||
v.SetString(tt);
|
||||
EventHandler.SendInterfaceEvent(consoleplayer,"swwmwpntooltip."..which.GetClassName());
|
||||
v = CVar.FindCVar('swwm_tooltipnote');
|
||||
if ( v.GetBool() ) return;
|
||||
v.SetBool(true);
|
||||
Console.Printf(StringTable.Localize("$SWWM_TTNOTE"));
|
||||
}
|
||||
|
||||
static clearscope bool CheatsDisabled( int p = -1 )
|
||||
{
|
||||
if ( cl_blockcheats || ((G_SkillPropertyInt(SKILLP_DisableCheats) || netgame || deathmatch) && !sv_cheats) )
|
||||
{
|
||||
if ( (p != -1) && (p == consoleplayer) )
|
||||
{
|
||||
Console.Printf("\cxSORRY NOTHING\c-");
|
||||
S_StartSound("misc/trombone",CHAN_VOICE,CHANF_UI);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Class EnvmapDebugSphere : Actor
|
||||
{
|
||||
override bool Used( Actor user )
|
||||
{
|
||||
if ( CurState.NextState )
|
||||
SetState(CurState.NextState);
|
||||
else SetState(SpawnState);
|
||||
return true;
|
||||
}
|
||||
override void Tick() {}
|
||||
Default
|
||||
{
|
||||
RenderStyle "Normal";
|
||||
Radius 16;
|
||||
Height 48;
|
||||
}
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
XZW1 A -1 NoDelay A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 B -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 C -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 D -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 E -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 F -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 G -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 H -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 I -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 J -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 K -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 L -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 M -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 N -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW1 O -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 P -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 Q -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 R -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 S -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 T -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 U -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 V -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 W -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 X -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 Y -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW1 Z -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW2 A -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW2 B -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW2 C -1 A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW2 D -1 A_SetRenderStyle(1.,STYLE_Add);
|
||||
XZW2 E -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
|
||||
XZW2 F -1 Bright A_SetRenderStyle(1.,STYLE_Add);
|
||||
Loop;
|
||||
}
|
||||
}
|
||||
86
zscript/utility/swwm_utility_achievements.zsc
Normal file
86
zscript/utility/swwm_utility_achievements.zsc
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// achievement helpers
|
||||
|
||||
extend class SWWMUtility
|
||||
{
|
||||
static clearscope void MarkAchievement( String mvar, PlayerInfo p = null )
|
||||
{
|
||||
if ( !p || (p != players[consoleplayer]) ) return;
|
||||
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
if ( !hnd ) return;
|
||||
String val = hnd.achievementstate.At(mvar);
|
||||
if ( val == "" )
|
||||
{
|
||||
if ( developer >= 2 ) Console.Printf("MarkAchievement: achievement '"..mvar.."' not found");
|
||||
return;
|
||||
}
|
||||
if ( val == "2" ) return;
|
||||
hnd.achievementstate.Insert(mvar,"1");
|
||||
}
|
||||
static clearscope int GetAchievementProgress( String pvar, PlayerInfo p = null )
|
||||
{
|
||||
if ( !p || (p != players[consoleplayer]) ) return 0;
|
||||
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
if ( !hnd ) return 0;
|
||||
String pval = hnd.achievementprogress.At(pvar);
|
||||
if ( pval == "" )
|
||||
{
|
||||
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
|
||||
return 0;
|
||||
}
|
||||
return pval.ToInt();
|
||||
}
|
||||
static clearscope void AchievementProgress( String pvar, int val, PlayerInfo p = null )
|
||||
{
|
||||
if ( !p || (p != players[consoleplayer]) ) return;
|
||||
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
if ( !hnd ) return;
|
||||
String pval = hnd.achievementprogress.At(pvar);
|
||||
if ( pval == "" )
|
||||
{
|
||||
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
|
||||
return;
|
||||
}
|
||||
if ( val <= pval.ToInt() ) return;
|
||||
hnd.achievementprogress.Insert(pvar,String.Format("%d",val));
|
||||
}
|
||||
static clearscope void AchievementProgressInc( String pvar, int inc, PlayerInfo p = null )
|
||||
{
|
||||
if ( !p || (p != players[consoleplayer]) ) return;
|
||||
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
if ( !hnd ) return;
|
||||
String pval = hnd.achievementprogress.At(pvar);
|
||||
if ( pval == "" )
|
||||
{
|
||||
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
|
||||
return;
|
||||
}
|
||||
hnd.achievementprogress.Insert(pvar,String.Format("%d",pval.ToInt()+inc));
|
||||
}
|
||||
static clearscope void AchievementProgressIncDouble( String pvar, double inc, PlayerInfo p = null )
|
||||
{
|
||||
if ( !p || (p != players[consoleplayer]) ) return;
|
||||
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
if ( !hnd ) return;
|
||||
String pval = hnd.achievementprogress.At(pvar);
|
||||
if ( pval == "" )
|
||||
{
|
||||
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
|
||||
return;
|
||||
}
|
||||
hnd.achievementprogress.Insert(pvar,String.Format("%g",pval.ToDouble()+inc));
|
||||
}
|
||||
// for bitfields
|
||||
static clearscope void AchievementProgressOr( String pvar, int val, PlayerInfo p = null )
|
||||
{
|
||||
if ( !p || (p != players[consoleplayer]) ) return;
|
||||
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
|
||||
if ( !hnd ) return;
|
||||
String pval = hnd.achievementprogress.At(pvar);
|
||||
if ( pval == "" )
|
||||
{
|
||||
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
|
||||
return;
|
||||
}
|
||||
hnd.achievementprogress.Insert(pvar,String.Format("%d",pval.ToInt()|val));
|
||||
}
|
||||
}
|
||||
252
zscript/utility/swwm_utility_blast.zsc
Normal file
252
zscript/utility/swwm_utility_blast.zsc
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
// explosion/knockback code
|
||||
enum EDoExplosionFlags
|
||||
{
|
||||
DE_BLAST = 1, // sets BLASTED flag on pushed actors
|
||||
DE_NOBLEED = 2, // does not spawn blood decals on hit
|
||||
DE_NOSPLASH = 4, // like XF_NOSPLASH
|
||||
DE_THRUWALLS = 8, // damages through geometry (no sight check)
|
||||
DE_NOTMISSILE = 16, // instigator is the source itself (normally it'd be its target pointer)
|
||||
DE_EXTRAZTHRUST = 32, // applies a higher Z thrust to enemies on ground
|
||||
DE_HOWL = 64, // 25% chance for hit enemies to howl
|
||||
DE_COUNTENEMIES = 128, // only count hits for hostiles
|
||||
DE_COUNTSTEALTH = 256, // only count hits for inactive monsters
|
||||
DE_COUNTFHKILLS = 512, // only count kills for enemies that were at full health
|
||||
DE_NOHURTFRIEND = 1024, // splash damage will not affect allies
|
||||
DE_CENTERHEIGHT = 2048, // origin of explosion is at the center height of the source actor, rather than its base
|
||||
DE_NONEXPLOSIVE = 4096, // does not count as explosive damage (DMG_EXPLOSION is not passed, and no blast taunts are used)
|
||||
DE_QUADRAVOL = 8192 // splash burn from a Quadravol projectile, so it'll ignite enemies instead of dealing damage
|
||||
};
|
||||
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// Apply full 3D knockback in a specific direction, useful for hitscan
|
||||
static play void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer, bool ExtraZThrust = false )
|
||||
{
|
||||
if ( !Victim )
|
||||
return;
|
||||
if ( Victim.bDORMANT ) // no dormant knockback
|
||||
return;
|
||||
if ( !Victim.bSHOOTABLE && !Victim.bVULNERABLE )
|
||||
return;
|
||||
if ( Victim.bDONTTHRUST || (Victim.Mass >= Actor.LARGE_MASS) )
|
||||
return;
|
||||
Vector3 Momentum = HitDirection*MomentumTransfer;
|
||||
if ( (Victim.pos.z <= Victim.floorz) || !Victim.TestMobjZ() )
|
||||
Momentum.z = max(Momentum.z,(ExtraZThrust?.4:.1)*Momentum.length());
|
||||
Momentum /= GameTicRate*max(50,Victim.Mass);
|
||||
Victim.vel += Momentum;
|
||||
}
|
||||
|
||||
// complete spherical and more accurate replacement of A_Explode
|
||||
// 100% free of the buggery GZDoom's own splash damage has
|
||||
// returns the number of shootables hit/killed
|
||||
static play int, int DoExplosion( Actor Source, double Damage, double MomentumTransfer, double ExplosionRadius, double FullDamageRadius = 0., int flags = 0, Name DamageType = '', Actor ignoreme = null, int dmgflags = 0, Actor realsource = null, Actor realinflictor = null )
|
||||
{
|
||||
FullDamageRadius = min(FullDamageRadius,ExplosionRadius);
|
||||
// debug, display radius sphere
|
||||
if ( swwm_debugblast )
|
||||
{
|
||||
let s = Actor.Spawn("RadiusDebugSphere",(flags&DE_CENTERHEIGHT)?Source.Vec3Offset(0,0,Source.height/2):Source.pos);
|
||||
s.Scale *= ExplosionRadius;
|
||||
s.SetShade((Damage>0)?"Green":"Blue");
|
||||
if ( FullDamageRadius > 0. )
|
||||
{
|
||||
let s = Actor.Spawn("RadiusDebugSphere",(flags&DE_CENTERHEIGHT)?Source.Vec3Offset(0,0,Source.height/2):Source.pos);
|
||||
s.Scale *= FullDamageRadius;
|
||||
s.SetShade("Red");
|
||||
}
|
||||
}
|
||||
if ( !(flags&DE_NOSPLASH) ) Source.CheckSplash(ExplosionRadius);
|
||||
double brange;
|
||||
// sanity checks are needed to avoid division by zero or other weirdness
|
||||
if ( ExplosionRadius == FullDamageRadius ) brange = 1.;
|
||||
else brange = 1./(ExplosionRadius-FullDamageRadius);
|
||||
Actor Instigator = realsource?realsource:(flags&DE_NOTMISSILE)?Source:Source.target;
|
||||
int dflg = ((flags&DE_NONEXPLOSIVE)?0:DMG_EXPLOSION)|dmgflags;
|
||||
BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius);
|
||||
int nhit = 0, nkill = 0;
|
||||
bool haskilled = false;
|
||||
Array<Actor> washit;
|
||||
washit.Clear();
|
||||
while ( bi.Next() )
|
||||
{
|
||||
Actor a = bi.Thing;
|
||||
washit.Push(a);
|
||||
// early checks for self and ignored actor (usually the instigator)
|
||||
if ( !a || (a == ignoreme) || (a == Source) )
|
||||
continue;
|
||||
// can't be affected
|
||||
if ( !a.bSHOOTABLE && !a.bVULNERABLE )
|
||||
continue;
|
||||
// no blasting if no radius dmg (unless forced)
|
||||
if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG )
|
||||
continue;
|
||||
// check the DONTHARMCLASS/DONTHARMSPECIES flags
|
||||
if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) )
|
||||
continue;
|
||||
// check friendliness
|
||||
if ( (flags&DE_NOHURTFRIEND) && Instigator && Instigator.IsFriend(a) )
|
||||
continue;
|
||||
// can we see it
|
||||
if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
|
||||
continue;
|
||||
// intersecting?
|
||||
if ( !SphereIntersect(a,Source.pos,ExplosionRadius) )
|
||||
continue;
|
||||
// calculate factor
|
||||
Vector3 dir;
|
||||
if ( flags&DE_CENTERHEIGHT ) dir = level.Vec3Diff(Source.Vec3Offset(0,0,Source.Height/2),a.Vec3Offset(0,0,a.Height/2));
|
||||
else dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2));
|
||||
double dist = dir.length();
|
||||
// intersecting, randomize direction
|
||||
if ( dir.length() <= double.epsilon )
|
||||
{
|
||||
double ang = FRandom[DoBlast](0,360);
|
||||
double pt = FRandom[DoBlast](-90,90);
|
||||
dir = Vec3FromAngles(ang,pt);
|
||||
}
|
||||
dir /= dist;
|
||||
dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius));
|
||||
double damagescale;
|
||||
if ( ExplosionRadius == FullDamageRadius ) damagescale = 1.;
|
||||
else damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.);
|
||||
double mm = MomentumTransfer*damagescale;
|
||||
// no knockback if massive/unpushable
|
||||
if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) )
|
||||
{
|
||||
Vector3 Momentum = dir*mm;
|
||||
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
|
||||
Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length());
|
||||
Momentum /= GameTicRate*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed
|
||||
a.vel += Momentum;
|
||||
if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true;
|
||||
}
|
||||
// hit it
|
||||
bool inactive = (!a.player&&!a.target);
|
||||
bool hostile = (Instigator&&a.IsHostile(Instigator)&&(a.bISMONSTER||a.player));
|
||||
if ( (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nhit++;
|
||||
int dmg = int(Damage*damagescale);
|
||||
if ( flags&DE_QUADRAVOL )
|
||||
{
|
||||
OnFire.Apply(a,Instigator,dmg); // ignite
|
||||
continue;
|
||||
}
|
||||
if ( dmg <= 0 ) continue; // no harm
|
||||
int oldhp = a.Health;
|
||||
int basehp = a.GetSpawnHealth();
|
||||
int ndmg = a.DamageMobj(realinflictor?realinflictor:Source,Instigator,dmg,(DamageType=='')?Source.DamageType:DamageType,dflg,atan2(-dir.y,-dir.x));
|
||||
if ( (ndmg > 0) && a && !(flags&DE_NOBLEED) ) a.TraceBleed(ndmg,Source);
|
||||
if ( (flags&DE_HOWL) && a && (a.Health > 0) && a.bISMONSTER && !Random[DoBlast](0,3) ) a.Howl();
|
||||
if ( hostile && (!a || (a.Health <= 0)) ) haskilled = true;
|
||||
if ( (flags&DE_COUNTFHKILLS) && (oldhp < basehp) ) continue; // was not at full health
|
||||
if ( (!a || (a.Health <= 0)) && (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nkill++;
|
||||
}
|
||||
// traverse portals (needed since BlockThingsIterator can't properly cross sector portals in both vertical directions)
|
||||
int thisgroup = Source.CurSector.portalgroup;
|
||||
for ( int i=0; i<level.GetPortalGroupCount(); i++ )
|
||||
{
|
||||
if ( i == thisgroup ) continue;
|
||||
Vector2 relpos = Source.pos.xy+level.GetDisplacement(thisgroup,i);
|
||||
bi = BlockThingsIterator.CreateFromPos(relpos.x,relpos.y,Source.pos.z,Source.pos.z+Source.height,ExplosionRadius,false);
|
||||
while ( bi.Next() )
|
||||
{
|
||||
Actor a = bi.Thing;
|
||||
// early exit for already processed actors
|
||||
if ( washit.Find(a) < washit.Size() )
|
||||
continue;
|
||||
washit.Push(a);
|
||||
// early checks for self and ignored actor (usually the instigator)
|
||||
if ( !a || (a == ignoreme) || (a == Source) )
|
||||
continue;
|
||||
// can't be affected
|
||||
if ( !a.bSHOOTABLE && !a.bVULNERABLE )
|
||||
continue;
|
||||
// no blasting if no radius dmg (unless forced)
|
||||
if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG )
|
||||
continue;
|
||||
// check the DONTHARMCLASS/DONTHARMSPECIES flags
|
||||
if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) )
|
||||
continue;
|
||||
// check friendliness
|
||||
if ( (flags&DE_NOHURTFRIEND) && Instigator && Instigator.IsFriend(a) )
|
||||
continue;
|
||||
// can we see it
|
||||
if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
|
||||
continue;
|
||||
// intersecting?
|
||||
if ( !SphereIntersect(a,Source.pos,ExplosionRadius) )
|
||||
continue;
|
||||
// calculate factor
|
||||
Vector3 dir;
|
||||
if ( flags&DE_CENTERHEIGHT ) dir = level.Vec3Diff(Source.Vec3Offset(0,0,Source.Height/2),a.Vec3Offset(0,0,a.Height/2));
|
||||
else dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2));
|
||||
double dist = dir.length();
|
||||
// intersecting, randomize direction
|
||||
if ( dir.length() <= double.epsilon )
|
||||
{
|
||||
double ang = FRandom[DoBlast](0,360);
|
||||
double pt = FRandom[DoBlast](-90,90);
|
||||
dir = Vec3FromAngles(ang,pt);
|
||||
}
|
||||
dir /= dist;
|
||||
dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius));
|
||||
double damagescale;
|
||||
if ( ExplosionRadius == FullDamageRadius ) damagescale = 1.;
|
||||
else damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.);
|
||||
double mm = MomentumTransfer*damagescale;
|
||||
// no knockback if massive/unpushable
|
||||
if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) )
|
||||
{
|
||||
Vector3 Momentum = dir*mm;
|
||||
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
|
||||
Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length());
|
||||
Momentum /= GameTicRate*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed
|
||||
a.vel += Momentum;
|
||||
if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true;
|
||||
}
|
||||
// hit it
|
||||
bool inactive = (!a.player&&!a.target);
|
||||
bool hostile = (Instigator&&a.IsHostile(Instigator)&&(a.bISMONSTER||a.player));
|
||||
if ( (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nhit++;
|
||||
int dmg = int(Damage*damagescale);
|
||||
if ( flags&DE_QUADRAVOL )
|
||||
{
|
||||
OnFire.Apply(a,Instigator,dmg); // ignite
|
||||
continue;
|
||||
}
|
||||
if ( dmg <= 0 ) continue; // no harm
|
||||
int oldhp = a.Health;
|
||||
int basehp = a.GetSpawnHealth();
|
||||
int ndmg = a.DamageMobj(realinflictor?realinflictor:Source,Instigator,dmg,(DamageType=='')?Source.DamageType:DamageType,dflg,atan2(-dir.y,-dir.x));
|
||||
if ( (ndmg > 0) && a && !(flags&DE_NOBLEED) ) a.TraceBleed(ndmg,Source);
|
||||
if ( (flags&DE_HOWL) && a && (a.Health > 0) && a.bISMONSTER && !Random[DoBlast](0,3) ) a.Howl();
|
||||
if ( hostile && (!a || (a.Health <= 0)) ) haskilled = true;
|
||||
if ( (flags&DE_COUNTFHKILLS) && (oldhp < basehp) ) continue; // was not at full health
|
||||
if ( (!a || (a.Health <= 0)) && (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nkill++;
|
||||
}
|
||||
}
|
||||
if ( (Instigator is 'Demolitionist') && haskilled && !(flags&DE_NONEXPLOSIVE) )
|
||||
{
|
||||
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
||||
let demo = Demolitionist(Instigator);
|
||||
if ( hnd && (gametic > demo.lastbang+30) && (gametic > hnd.lastcombat+10) && !Random[DemoLines](0,1) )
|
||||
demo.lastbang = SWWMHandler.AddOneLiner("blast",2,10);
|
||||
}
|
||||
return nhit, nkill;
|
||||
}
|
||||
}
|
||||
|
||||
Class RadiusDebugSphere : SWWMNonInteractiveActor
|
||||
{
|
||||
Default
|
||||
{
|
||||
RenderStyle "AddStencil";
|
||||
StencilColor "White";
|
||||
}
|
||||
States
|
||||
{
|
||||
Spawn:
|
||||
XZW1 A 1 BRIGHT A_FadeOut();
|
||||
Wait;
|
||||
}
|
||||
}
|
||||
153
zscript/utility/swwm_utility_compat.zsc
Normal file
153
zscript/utility/swwm_utility_compat.zsc
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// compat / detection code
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// try to be as thorough as possible in checking if DEHACKED has altered this actor
|
||||
static clearscope bool CheckDehackery( Class<Actor> cls )
|
||||
{
|
||||
let def = GetDefaultByType(cls);
|
||||
for ( State s=def.SpawnState; s; s=s.NextState )
|
||||
{
|
||||
if ( s.bDEHACKED ) return true;
|
||||
// keep checking until we hit a loop, just in case
|
||||
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
|
||||
}
|
||||
for ( State s=def.SeeState; s; s=s.NextState )
|
||||
{
|
||||
if ( s.bDEHACKED ) return true;
|
||||
// keep checking until we hit a loop, just in case
|
||||
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
|
||||
}
|
||||
for ( State s=def.MissileState; s; s=s.NextState )
|
||||
{
|
||||
if ( s.bDEHACKED ) return true;
|
||||
// keep checking until we hit a loop, just in case
|
||||
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
|
||||
}
|
||||
for ( State s=def.MeleeState; s; s=s.NextState )
|
||||
{
|
||||
if ( s.bDEHACKED ) return true;
|
||||
// keep checking until we hit a loop, just in case
|
||||
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// shorthand for some of these checks (these are generally used by the mission briefing system)
|
||||
static bool IsKnownMap()
|
||||
{
|
||||
if ( (gameinfo.gametype&GAME_DOOM) && (IsKnownCustomWAD() || CheckMD5List("vanilladoom.lst")) )
|
||||
return true;
|
||||
if ( (gameinfo.gametype&GAME_HERETIC) && CheckMD5List("vanillaheretic.lst") )
|
||||
return true;
|
||||
if ( (gameinfo.gametype&GAME_HEXEN) && CheckMD5List("vanillahexen.lst") )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// to be filled
|
||||
static bool IsKnownCustomWAD()
|
||||
{
|
||||
if ( gameinfo.gametype&GAME_DOOM )
|
||||
{
|
||||
if ( IsEviternity() ) return true;
|
||||
if ( IsUltDoom2() ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// detect ultimate doom 2
|
||||
static bool IsUltDoom2()
|
||||
{
|
||||
return CheckMD5List("ultdoom2.lst");
|
||||
}
|
||||
|
||||
// detect eviternity (naive method)
|
||||
static bool IsEviternity()
|
||||
{
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
if ( cls.GetClassName() != "Archangelus" )
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// detect doom vacation
|
||||
static bool InDoomVacation()
|
||||
{
|
||||
// cheap, but hey, it should work
|
||||
if ( Wads.FindLump("VACABEX") != -1 )
|
||||
{
|
||||
// just to make sure
|
||||
foreach ( cls:AllActorClasses )
|
||||
{
|
||||
if ( cls.GetClassName() != "Babe" )
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsDeathkings()
|
||||
{
|
||||
if ( (gameinfo.gametype&GAME_HEXEN) && LevelInfo.MapExists("MAP41") )
|
||||
{
|
||||
let inf = LevelInfo.FindLevelInfo("MAP41");
|
||||
if ( inf.cluster == 1 ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// detect vanilla maps (across all IWAD versions)
|
||||
static bool CheckMD5List( String fname )
|
||||
{
|
||||
String csum = level.GetChecksum();
|
||||
let lmp = Wads.CheckNumForFullName(fname);
|
||||
if ( lmp == -1 ) return false;
|
||||
String dat = Wads.ReadLump(lmp);
|
||||
// fucking Windows
|
||||
dat.Replace("\r","");
|
||||
Array<String> list;
|
||||
list.Clear();
|
||||
dat.Split(list,"\n");
|
||||
foreach ( l:list )
|
||||
{
|
||||
if ( (l.Length() == 0) || (l.Left(1) == "#") || (l.Left(1) == "") )
|
||||
continue;
|
||||
if ( csum ~== l )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// WHACK
|
||||
static play void EndLevelDie( Actor victim )
|
||||
{
|
||||
victim.DamageMobj(null,null,victim.Health,'EndLevel',DMG_FORCED|DMG_THRUSTLESS);
|
||||
}
|
||||
|
||||
// for Equinox
|
||||
static play void SpawnVanillaBossBrain( int tid )
|
||||
{
|
||||
let ai = Level.CreateActorIterator(tid);
|
||||
Actor a;
|
||||
while ( a = ai.Next() )
|
||||
{
|
||||
let bb = a.Spawn("BossBrain",a.pos,NO_REPLACE);
|
||||
bb.angle = a.angle;
|
||||
}
|
||||
}
|
||||
|
||||
// checks if we're playing in doom 1
|
||||
// this is used so we can sometimes replace the shotgun with a SSG slot weapon
|
||||
static bool IsDoomOne()
|
||||
{
|
||||
if ( !(gameinfo.GameType&GAME_DOOM) ) return false;
|
||||
// is the map in ExMx format? Then it's likely we're playing a doom 1 map
|
||||
if ( (level.mapname.Length() >= 4) && (level.mapname.Mid(0,1) == "E") && (level.mapname.ByteAt(1) >= 0x30) && (level.mapname.ByteAt(1) < 0x40) && (level.mapname.Mid(2,1) == "M") && (level.mapname.ByteAt(3) >= 0x30) && (level.mapname.ByteAt(3) < 0x40) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
440
zscript/utility/swwm_utility_info.zsc
Normal file
440
zscript/utility/swwm_utility_info.zsc
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
// identification and tagging
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// because GetTag() returns the localized string, we need to do things the hard way
|
||||
static play String GetFunTag( SWWMHandler hnd, Actor a, String defstr = "" )
|
||||
{
|
||||
// look up fun tag services if available
|
||||
foreach ( sv:hnd.funtagsv )
|
||||
{
|
||||
if ( !sv ) continue;
|
||||
String res = sv.GetString("GetFunTag",objectArg:a);
|
||||
if ( res == "" ) continue;
|
||||
return res;
|
||||
}
|
||||
int ntags = 1;
|
||||
String basetag = "";
|
||||
switch ( a.GetClassName() )
|
||||
{
|
||||
// Doom
|
||||
case 'ZombieMan':
|
||||
case 'StealthZombieMan':
|
||||
basetag = "ZOMBIE";
|
||||
break;
|
||||
case 'ShotgunGuy':
|
||||
case 'StealthShotgunGuy':
|
||||
basetag = "SHOTGUN";
|
||||
break;
|
||||
case 'ChaingunGuy':
|
||||
case 'StealthChaingunGuy':
|
||||
basetag = "HEAVY";
|
||||
break;
|
||||
case 'DoomImp':
|
||||
case 'StealthDoomImp':
|
||||
basetag = "IMP";
|
||||
break;
|
||||
case 'Demon':
|
||||
case 'StealthDemon':
|
||||
basetag = "DEMON";
|
||||
break;
|
||||
case 'Spectre':
|
||||
basetag = "SPECTRE";
|
||||
break;
|
||||
case 'LostSoul':
|
||||
basetag = "LOST";
|
||||
break;
|
||||
case 'Cacodemon':
|
||||
case 'StealthCacodemon':
|
||||
basetag = "CACO";
|
||||
break;
|
||||
case 'HellKnight':
|
||||
case 'StealthHellKnight':
|
||||
basetag = "HELL";
|
||||
break;
|
||||
case 'BaronOfHell':
|
||||
case 'StealthBaron':
|
||||
basetag = "BARON";
|
||||
break;
|
||||
case 'Arachnotron':
|
||||
case 'StealthArachnotron':
|
||||
basetag = "ARACH";
|
||||
break;
|
||||
case 'PainElemental':
|
||||
basetag = "PAIN";
|
||||
break;
|
||||
case 'Revenant':
|
||||
case 'StealthRevenant':
|
||||
basetag = "REVEN";
|
||||
break;
|
||||
case 'Fatso':
|
||||
case 'StealthFatso':
|
||||
basetag = "MANCU";
|
||||
break;
|
||||
case 'Archvile':
|
||||
case 'StealthArchvile':
|
||||
basetag = "ARCH";
|
||||
break;
|
||||
case 'SpiderMastermind':
|
||||
basetag = "SPIDER";
|
||||
break;
|
||||
case 'Cyberdemon':
|
||||
basetag = "CYBER";
|
||||
break;
|
||||
case 'SWWMBossBrain':
|
||||
basetag = "BOSSBRAIN";
|
||||
break;
|
||||
case 'WolfensteinSS':
|
||||
if ( IsUltDoom2() )
|
||||
{
|
||||
basetag = "ELITEZOMBIE";
|
||||
break;
|
||||
}
|
||||
// ensure it's not being replaced
|
||||
if ( CheckDehackery('WolfensteinSS') ) break;
|
||||
case 'SWWMSS':
|
||||
basetag = "WOLFSS";
|
||||
break;
|
||||
case 'SWWMHangingKeen':
|
||||
basetag = "KEEN";
|
||||
break;
|
||||
case 'MBFHelperDog':
|
||||
case 'SWWMDog':
|
||||
basetag = "DOG";
|
||||
break;
|
||||
case 'SWWMGuard':
|
||||
basetag = "WOLFGUARD";
|
||||
break;
|
||||
case 'SWWMHans':
|
||||
basetag = "WOLFHANS";
|
||||
break;
|
||||
// Heretic
|
||||
case 'Chicken':
|
||||
basetag = "CHICKEN";
|
||||
break;
|
||||
case 'Beast':
|
||||
basetag = "BEAST";
|
||||
break;
|
||||
case 'Clink':
|
||||
basetag = "CLINK";
|
||||
break;
|
||||
case 'Sorcerer1':
|
||||
case 'Sorcerer2':
|
||||
basetag = "DSPARIL";
|
||||
break;
|
||||
case 'HereticImp':
|
||||
case 'HereticImpLeader':
|
||||
basetag = "HERETICIMP";
|
||||
break;
|
||||
case 'Ironlich':
|
||||
basetag = "IRONLICH";
|
||||
break;
|
||||
case 'Knight':
|
||||
case 'KnightGhost':
|
||||
basetag = "BONEKNIGHT";
|
||||
break;
|
||||
case 'Minotaur':
|
||||
case 'MinotaurFriend':
|
||||
basetag = "MINOTAUR";
|
||||
break;
|
||||
case 'Mummy':
|
||||
case 'MummyGhost':
|
||||
basetag = "MUMMY";
|
||||
break;
|
||||
case 'MummyLeader':
|
||||
case 'MummyLeaderGhost':
|
||||
basetag = "MUMMYLEADER";
|
||||
break;
|
||||
case 'Snake':
|
||||
basetag = "SNAKE";
|
||||
break;
|
||||
case 'Wizard':
|
||||
basetag = "WIZARD";
|
||||
break;
|
||||
// Hexen
|
||||
case 'FireDemon':
|
||||
basetag = "FIREDEMON";
|
||||
break;
|
||||
case 'Demon1':
|
||||
case 'Demon1Mash':
|
||||
case 'Demon2':
|
||||
case 'Demon2Mash':
|
||||
basetag = "DEMON1";
|
||||
break;
|
||||
case 'Ettin':
|
||||
case 'EttinMash':
|
||||
basetag = "ETTIN";
|
||||
break;
|
||||
case 'Centaur':
|
||||
case 'CentaurMash':
|
||||
basetag = "CENTAUR";
|
||||
break;
|
||||
case 'CentaurLeader':
|
||||
basetag = "SLAUGHTAUR";
|
||||
break;
|
||||
case 'Bishop':
|
||||
basetag = "BISHOP";
|
||||
break;
|
||||
case 'IceGuy':
|
||||
basetag = "ICEGUY";
|
||||
break;
|
||||
case 'Serpent':
|
||||
case 'SerpentLeader':
|
||||
basetag = "SERPENT";
|
||||
break;
|
||||
case 'Wraith':
|
||||
case 'WraithBuried':
|
||||
basetag = "WRAITH";
|
||||
break;
|
||||
case 'Dragon':
|
||||
basetag = "DRAGON";
|
||||
break;
|
||||
case 'Korax':
|
||||
basetag = "KORAX";
|
||||
break;
|
||||
case 'FighterBoss':
|
||||
basetag = "FBOSS";
|
||||
break;
|
||||
case 'MageBoss':
|
||||
basetag = "MBOSS";
|
||||
break;
|
||||
case 'ClericBoss':
|
||||
basetag = "CBOSS";
|
||||
break;
|
||||
case 'Heresiarch':
|
||||
basetag = "HERESIARCH";
|
||||
break;
|
||||
case 'Pig':
|
||||
basetag = "PIG";
|
||||
break;
|
||||
// eviternity
|
||||
case 'ArchangelusA':
|
||||
case 'ArchangelusB':
|
||||
basetag = "ANGEL";
|
||||
break;
|
||||
case 'AstralCaco':
|
||||
basetag = "ASTRAL";
|
||||
break;
|
||||
case 'Annihilator':
|
||||
basetag = "ANNIHIL";
|
||||
break;
|
||||
case 'FormerCaptain':
|
||||
basetag = "FCAPTAIN";
|
||||
break;
|
||||
case 'NightmareDemon':
|
||||
basetag = "NDEMON";
|
||||
break;
|
||||
}
|
||||
if ( basetag == "" ) return a.GetTag(defstr);
|
||||
String funtag = "FN_"..basetag.."_FUN";
|
||||
String lfuntag = StringTable.Localize(funtag,false);
|
||||
if ( lfuntag != funtag ) return lfuntag;
|
||||
String nfuntag = "FN_"..basetag.."_FUNN";
|
||||
String lnfuntag = StringTable.Localize(nfuntag,false);
|
||||
if ( lnfuntag == nfuntag ) return a.GetTag(defstr);
|
||||
ntags = lnfuntag.ToInt();
|
||||
return StringTable.Localize(String.Format("$FN_%s_FUN%d",basetag,Random[FunTags](1,ntags)));
|
||||
}
|
||||
|
||||
// used to "substitute" a monster class for another so killcount stats are merged
|
||||
// e.g.: "stealth" monsters and their non-stealth counterparts,
|
||||
// or "HereticImp" and "HereticImpLeader", which have the same exact tag,
|
||||
// and would result in an odd "duplication" of monster names
|
||||
static play Class<Actor> MergeMonster( SWWMHandler hnd, Class<Actor> a )
|
||||
{
|
||||
// see if any services can resolve this first
|
||||
foreach ( sv:hnd.mergemonstersv )
|
||||
{
|
||||
if ( !sv ) continue;
|
||||
String res = sv.GetString("MergeMonster",stringArg:a.GetClassName());
|
||||
if ( res == "" ) continue;
|
||||
Class<Actor> rescls = res;
|
||||
return rescls;
|
||||
}
|
||||
// stealth monsters, the worst thing ever invented
|
||||
if ( a == 'StealthArachnotron' ) return 'Arachnotron';
|
||||
if ( a == 'StealthArchvile' ) return 'Archvile';
|
||||
if ( a == 'StealthBaron' ) return 'BaronOfHell';
|
||||
if ( a == 'StealthCacodemon' ) return 'Cacodemon';
|
||||
if ( a == 'StealthChaingunGuy' ) return 'ChaingunGuy';
|
||||
if ( a == 'StealthDemon' ) return 'Demon';
|
||||
if ( a == 'StealthHellKnight' ) return 'HellKnight';
|
||||
if ( a == 'StealthDoomImp' ) return 'DoomImp';
|
||||
if ( a == 'StealthFatso' ) return 'Fatso';
|
||||
if ( a == 'StealthRevenant' ) return 'Revenant';
|
||||
if ( a == 'StealthShotgunGuy' ) return 'ShotgunGuy';
|
||||
if ( a == 'StealthZombieMan' ) return 'ZombieMan';
|
||||
// heretic monsters
|
||||
if ( a == 'Sorcerer2' ) return 'Sorcerer1';
|
||||
if ( a == 'HereticImpLeader' ) return 'HereticImp';
|
||||
if ( a == 'KnightGhost' ) return 'Knight';
|
||||
if ( a == 'MummyGhost' ) return 'Mummy';
|
||||
if ( a == 'MummyLeaderGhost' ) return 'MummyLeader';
|
||||
// hexen monsters
|
||||
if ( a == 'CentaurMash' ) return 'Centaur';
|
||||
if ( a == 'Demon1Mash' ) return 'Demon1';
|
||||
if ( a == 'Demon2' ) return 'Demon1';
|
||||
if ( a == 'Demon2Mash' ) return 'Demon1';
|
||||
if ( a == 'EttinMash' ) return 'Ettin';
|
||||
if ( a == 'SerpentLeader' ) return 'Serpent';
|
||||
if ( a == 'WraithBuried' ) return 'Wraith';
|
||||
return a;
|
||||
}
|
||||
|
||||
// gendered languages are a fuck
|
||||
static clearscope bool SellFemaleItem( Inventory i, String loc = "SWWM_SELLEXTRA_FEM" )
|
||||
{
|
||||
// no gendered string alt
|
||||
if ( StringTable.Localize("$"..loc) == loc )
|
||||
return false;
|
||||
if ( i is 'DeepImpact' ) return true;
|
||||
if ( i is 'ExplodiumGun' ) return true;
|
||||
if ( i is 'Wallbuster' ) return true;
|
||||
if ( i is 'HeavyMahSheenGun' ) return true;
|
||||
if ( i is 'Quadravol' ) return true;
|
||||
if ( i is 'Sparkster' ) return true;
|
||||
if ( i is 'CandyGun' ) return true;
|
||||
if ( i is 'RayKhom' ) return true;
|
||||
if ( i is 'RafanKos' ) return true;
|
||||
if ( i is 'HealthNuggetItem' ) return true;
|
||||
if ( i is 'ArmorNuggetItem' ) return true;
|
||||
if ( i is 'WarArmor' ) return true;
|
||||
if ( i is 'FuckingInvinciball' ) return true;
|
||||
if ( i is 'SWWMLamp' ) return true;
|
||||
if ( i is 'AngerySigil' ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns the plural tag (if available)
|
||||
static clearscope string GetAmmoTag( Inventory i )
|
||||
{
|
||||
if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..MagAmmo(i).PickupTag.."S");
|
||||
if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..SWWMAmmo(i).PickupTag.."S");
|
||||
return i.GetTag();
|
||||
}
|
||||
// because of zscript fuckery with GetDefaultByType
|
||||
static clearscope string GetAmmoTagClass( Class<Inventory> i )
|
||||
{
|
||||
if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class<MagAmmo>)(i)).PickupTag.."S");
|
||||
if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class<SWWMAmmo>)(i)).PickupTag.."S");
|
||||
return GetDefaultByType(i).GetTag();
|
||||
}
|
||||
|
||||
// IsZeroDamage() can lead to some false negatives, we have to account for that
|
||||
static play bool ValidProjectile( Actor a )
|
||||
{
|
||||
if ( !a.bMISSILE ) return false;
|
||||
if ( a is 'AirBullet' ) return true;
|
||||
if ( a is 'ExplodiumMagProj' ) return true;
|
||||
if ( a is 'TheBall' ) return true;
|
||||
if ( a is 'EvisceratorChunk' ) return true;
|
||||
if ( a is 'EvisceratorProj' ) return true;
|
||||
if ( a is 'HellblazerMissile' ) return true;
|
||||
if ( a is 'QuadProj' ) return true;
|
||||
if ( a is 'BigBiospark' ) return true;
|
||||
if ( a is 'BiosparkBall' ) return true;
|
||||
if ( a is 'BiosparkCore' ) return true;
|
||||
if ( a is 'CandyGunProj' ) return true;
|
||||
if ( a is 'CandyMagProj' ) return true;
|
||||
if ( a is 'MisterGrenade' ) return true;
|
||||
if ( a is 'LoveHeart' ) return true;
|
||||
if ( !a.IsZeroDamage() ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is this a beam projectile? (speed == length)
|
||||
static play bool IsBeamProj( Actor a )
|
||||
{
|
||||
if ( a is 'SaltBeam' ) return true;
|
||||
if ( a is 'BiosparkBeam' ) return true;
|
||||
if ( a is 'BiosparkArc' ) return true;
|
||||
if ( a is 'CandyBeam' ) return true;
|
||||
if ( a is 'YnykronBeam' ) return true;
|
||||
if ( a is 'YnykronLightningArc' ) return true;
|
||||
if ( a is 'YnykronAltBeam' ) return true;
|
||||
if ( a is 'MykradvoTendril' ) return true;
|
||||
if ( a is 'MisterRailBeam' ) return true;
|
||||
return false;
|
||||
}
|
||||
// is this a YBeam type? (real pitch is pitch-90)
|
||||
static play bool IsYBeam( Actor a )
|
||||
{
|
||||
if ( a is 'MisterRailBeam' ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static clearscope bool IdentifyingDog( Actor a )
|
||||
{
|
||||
if ( a is 'MBFHelperDog' ) return true;
|
||||
if ( a is 'SWWMDog' ) return true;
|
||||
if ( a.GetClassName() == 'GermanDog' ) return true; // brote dote
|
||||
if ( a.GetClassName() == '64HellHound' ) return true; // brote dote 64
|
||||
if ( a.GetClassName() == 'AbyssDemon2' ) return true; // CH
|
||||
if ( a.GetClassName() == 'WHOLETTHEDOGSOUT' ) return true; // CH
|
||||
// more dogs will be added as found
|
||||
// because all dogs must be pet
|
||||
return false;
|
||||
}
|
||||
|
||||
static clearscope bool IdentifyingCaco( Actor a )
|
||||
{
|
||||
if ( a is 'DeadCacodemon' ) return false;
|
||||
if ( a is 'Cacodemon' ) return true;
|
||||
if ( a.Species == 'RLCacodemon' ) return true; // DRLA
|
||||
if ( a.Species == 'Caco' ) return true; // CH and others
|
||||
if ( a.Species == 'Cacodemon' ) return true; // Beautiful Doom
|
||||
if ( a.GetClassName() == 'AstralCaco' ) return true; // Eviternity
|
||||
if ( a.GetParentClass().GetClassName() == 'LEG_BaseCaco' ) return true; // LEGION
|
||||
return false;
|
||||
}
|
||||
|
||||
// Друг
|
||||
static clearscope bool IdentifyingDrug( Actor a )
|
||||
{
|
||||
if ( a is 'Beast' ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static clearscope bool IdentifyingDoubleBoi( Actor a )
|
||||
{
|
||||
if ( a is 'Ettin' ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsVipItem( Actor target )
|
||||
{
|
||||
if ( (target is 'Chancebox') && (target.CurState==target.SpawnState) )
|
||||
return true;
|
||||
if ( target is 'SWWMCollectible' )
|
||||
return true;
|
||||
if ( (target is 'Ynykron') || (target is 'RafanKos') )
|
||||
return true;
|
||||
if ( (target is 'GoldShell') || (target is 'YnykronAmmo') || (target is 'UltimatePod') || (target is 'UltimateAmmo') )
|
||||
return true;
|
||||
if ( (target is 'Mykradvo') || (target is 'AngerySigil') || (target is 'DivineSprite') )
|
||||
return true;
|
||||
if ( target is 'PuzzleItem' )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// used by the store
|
||||
static bool IsVipItemClass( Class<Actor> target )
|
||||
{
|
||||
if ( (target is 'Ynykron') || (target is 'RafanKos') )
|
||||
return true;
|
||||
if ( (target is 'GoldShell') || (target is 'YnykronAmmo') || (target is 'UltimatePod') || (target is 'UltimateAmmo') )
|
||||
return true;
|
||||
if ( (target is 'Mykradvo') || (target is 'AngerySigil') || (target is 'DivineSprite') )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsScoreItem( Actor target )
|
||||
{
|
||||
if ( target is 'Key' )
|
||||
return true;
|
||||
if ( target is 'HammerspaceEmbiggener' )
|
||||
return true;
|
||||
return target.bCOUNTITEM;
|
||||
}
|
||||
}
|
||||
327
zscript/utility/swwm_utility_item.zsc
Normal file
327
zscript/utility/swwm_utility_item.zsc
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
// inventory-related functions
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// full reset of inventory (excluding collectibles, and optionally resetting the score)
|
||||
static play void WipeInventory( Actor mo, bool resetscore = false, bool allplayers = false )
|
||||
{
|
||||
if ( allplayers )
|
||||
{
|
||||
for ( int i=0; i<MAXPLAYERS; i++ )
|
||||
{
|
||||
if ( !playeringame[i] || !players[i].mo ) continue;
|
||||
WipeInventory(players[i].mo,resetscore,false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
PlayerInfo p = mo.player;
|
||||
if ( !p || !p.mo ) return;
|
||||
SWWMCredits c = SWWMCredits.Find(p);
|
||||
if ( resetscore && c ) c.credits = 0;
|
||||
Actor last = p.mo;
|
||||
while ( last.inv )
|
||||
{
|
||||
let inv = last.inv;
|
||||
if ( !(inv is 'SWWMCollectible') )
|
||||
{
|
||||
inv.Destroy();
|
||||
if ( !inv.bDestroyed ) last = inv;
|
||||
}
|
||||
else last = inv;
|
||||
}
|
||||
p.mo.GiveDefaultInventory();
|
||||
p.mo.BringUpWeapon();
|
||||
p.health = p.mo.Health = p.mo.SpawnHealth();
|
||||
}
|
||||
|
||||
// sets all carried ammo back to zero
|
||||
// resets hammerspace capacity
|
||||
static play void ResetAmmo( Actor mo )
|
||||
{
|
||||
PlayerInfo p = mo.player;
|
||||
if ( !p || !p.mo ) return;
|
||||
for ( Inventory i=p.mo.inv; i; i=i.inv )
|
||||
{
|
||||
if ( (i is 'Ammo') || (i is 'MagAmmo') )
|
||||
{
|
||||
i.Amount = 0;
|
||||
i.MaxAmount = i.default.MaxAmount;
|
||||
}
|
||||
if ( i is 'HammerspaceEmbiggener' )
|
||||
i.Amount = 0;
|
||||
}
|
||||
// also gives back any ammo from carried weapons (provided they don't have NOFIRSTGIVE)
|
||||
for ( Inventory i=p.mo.inv; i; i=i.inv )
|
||||
{
|
||||
if ( !(i is 'Weapon') ) continue;
|
||||
let w = Weapon(i);
|
||||
if ( (w is 'SWWMWeapon') && SWWMWeapon(w).bNOFIRSTGIVE ) continue;
|
||||
if ( w.Ammo1 ) w.Ammo1.Amount = min(w.Ammo1.MaxAmount,w.Ammo1.Amount+w.default.AmmoGive1);
|
||||
if ( w.Ammo2 && (w.Ammo2 != w.Ammo1) ) w.Ammo2.Amount = min(w.Ammo2.MaxAmount,w.Ammo2.Amount+w.default.AmmoGive2);
|
||||
}
|
||||
}
|
||||
|
||||
// removes all usable items
|
||||
static play void ResetItems( Actor mo )
|
||||
{
|
||||
PlayerInfo p = mo.player;
|
||||
if ( !p || !p.mo ) return;
|
||||
Actor last = p.mo;
|
||||
while ( last.inv )
|
||||
{
|
||||
let inv = last.inv;
|
||||
if ( !inv.bINVBAR )
|
||||
{
|
||||
last = inv;
|
||||
continue;
|
||||
}
|
||||
inv.Destroy();
|
||||
if ( !inv.bDestroyed ) last = inv;
|
||||
}
|
||||
}
|
||||
|
||||
// resets health and removes worn armor
|
||||
static play void ResetHealth( Actor mo )
|
||||
{
|
||||
PlayerInfo p = mo.player;
|
||||
if ( !p || !p.mo ) return;
|
||||
p.health = p.mo.health = 100;
|
||||
for ( Inventory i=p.mo.inv; i; i=i.inv )
|
||||
{
|
||||
if ( !(i is 'SWWMArmor') ) continue;
|
||||
i.Amount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// what RandomSpawner does, basically (simplified for items)
|
||||
static play void TransferItemProp( Actor a, Actor b, bool bundlehack = false )
|
||||
{
|
||||
if ( bundlehack )
|
||||
{
|
||||
b.spawnpoint = b.pos;
|
||||
b.spawnangle = int(b.angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.spawnpoint = a.spawnpoint;
|
||||
b.spawnangle = a.spawnangle;
|
||||
b.angle = a.angle;
|
||||
b.pitch = a.pitch;
|
||||
b.roll = a.roll;
|
||||
}
|
||||
b.special = a.special;
|
||||
b.FloatBobPhase = a.FloatBobPhase; // important
|
||||
for ( int i=0; i<5; i++ ) b.args[i] = a.args[i];
|
||||
b.special1 = a.special1;
|
||||
b.special2 = a.special2;
|
||||
b.spawnflags = a.spawnflags&~MTF_SECRET;
|
||||
b.HandleSpawnFlags();
|
||||
b.spawnflags = a.spawnflags;
|
||||
b.bCountSecret = a.spawnflags&MTF_SECRET;
|
||||
b.ChangeTid(a.tid);
|
||||
b.vel = b.vel;
|
||||
b.master = b.master;
|
||||
b.tracer = b.tracer;
|
||||
b.target = b.target;
|
||||
b.bDROPPED = a.bDROPPED;
|
||||
}
|
||||
|
||||
// check that all players can get enough of this if needed
|
||||
// multi: check for multiple copies, not just single instances
|
||||
// (useful e.g. for dual wieldable weapons)
|
||||
static bool CheckNeedsItem( Class<Inventory> itm, bool multi = false )
|
||||
{
|
||||
int np = 0;
|
||||
for ( int i=0; i<MAXPLAYERS; i++ )
|
||||
{
|
||||
if ( !playeringame[i] || !players[i].mo ) continue;
|
||||
np++;
|
||||
}
|
||||
int required = np;
|
||||
if ( multi ) required *= GetDefaultByType(itm).MaxAmount;
|
||||
// subtract all that exist already (either in world or owned)
|
||||
let ti = ThinkerIterator.Create(itm);
|
||||
Inventory i;
|
||||
while ( i = Inventory(ti.Next()) )
|
||||
{
|
||||
if ( multi ) required -= i.Amount;
|
||||
else required--;
|
||||
}
|
||||
// check travelling inventory separately, as by default iterators don't check anything below STAT_FIRST_THINKING
|
||||
ti = ThinkerIterator.Create(itm,Thinker.STAT_TRAVELLING);
|
||||
while ( i = Inventory(ti.Next()) )
|
||||
{
|
||||
if ( multi ) required -= i.Amount;
|
||||
else required--;
|
||||
}
|
||||
return (required>0);
|
||||
}
|
||||
|
||||
// checks if instances of a certain item exist
|
||||
// skipme: optionally, ignore checking for one specific instance
|
||||
// (useful to check if we're the only copy of an item)
|
||||
// mapstart: this function is being called during map load, so we
|
||||
// should also check STAT_TRAVELLING inventory
|
||||
// worldonly: only checks for items that are placed in the world
|
||||
// ownedonly: only checks for items that are owned by players
|
||||
// (note that this is mutually exclusive with worldonly)
|
||||
static bool ItemExists( Class<Inventory> itm, Inventory skipme = null, bool mapstart = false, bool worldonly = false, bool ownedonly = false )
|
||||
{
|
||||
let ti = ThinkerIterator.Create(itm);
|
||||
Inventory i;
|
||||
while ( i = Inventory(ti.Next()) )
|
||||
{
|
||||
if ( i == skipme ) continue;
|
||||
if ( worldonly && i.Owner ) continue;
|
||||
if ( ownedonly && (!i.Owner || !i.Owner.player) ) continue;
|
||||
return true;
|
||||
}
|
||||
if ( worldonly || !mapstart ) return false;
|
||||
ti = ThinkerIterator.Create(itm,Thinker.STAT_TRAVELLING);
|
||||
while ( i = Inventory(ti.Next()) )
|
||||
{
|
||||
if ( i == skipme ) continue;
|
||||
if ( ownedonly && (!i.Owner || !i.Owner.player) ) continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// multi-weapon spawn stuff
|
||||
|
||||
static private Class<Weapon> PickPair( Class<Weapon> a, Class<Weapon> b )
|
||||
{
|
||||
if ( !ItemExists(a,mapstart:true) ) return a;
|
||||
if ( !ItemExists(b,mapstart:true) ) return b;
|
||||
return Random[Replacements](0,1)?a:b;
|
||||
}
|
||||
|
||||
// melee weapon + extra slot 2 guns
|
||||
static Class<Weapon> PickSWWMSlot1()
|
||||
{
|
||||
// [GROSS HACK] default to a hammer if there are no players
|
||||
// (this genuinely can happen, if player starts were placed AFTER the item)
|
||||
int np = 0;
|
||||
for ( int i=0; i<MAXPLAYERS; i++ )
|
||||
{
|
||||
if ( !playeringame[i] || !players[i].mo ) continue;
|
||||
np++;
|
||||
}
|
||||
if ( np <= 0 ) return 'ItamexHammer';
|
||||
// so the player can recover it if they decided to drop it in a previous map, or they didn't start with it
|
||||
if ( CheckNeedsItem('DeepImpact') ) return 'DeepImpact';
|
||||
if ( CheckNeedsItem('ItamexHammer') ) return 'ItamexHammer';
|
||||
return PickSWWMSlot2();
|
||||
}
|
||||
// pistol spawn, pretty simple
|
||||
static Class<Weapon> PickSWWMSlot2()
|
||||
{
|
||||
// as they are dual-wieldable, there should be a 50% chance for spares to also appear if needed
|
||||
if ( Random[Replacements](0,1) && !CheckNeedsItem('ExplodiumGun') && CheckNeedsItem('ExplodiumGun',true) )
|
||||
return 'ExplodiumGun';
|
||||
if ( Random[Replacements](0,1) && !CheckNeedsItem('PlasmaBlast') && CheckNeedsItem('PlasmaBlast',true) )
|
||||
return 'PlasmaBlast';
|
||||
return PickPair('ExplodiumGun','PlasmaBlast');
|
||||
}
|
||||
// shotgun spawn
|
||||
static Class<Weapon> PickSWWMSlot3()
|
||||
{
|
||||
return PickPair('Spreadgun','PuntzerBeta');
|
||||
}
|
||||
// super shotgun spawn
|
||||
static Class<Weapon> PickSWWMSlot4()
|
||||
{
|
||||
return PickPair('Wallbuster','PuntzerGamma');
|
||||
}
|
||||
// chaingun spawn
|
||||
static Class<Weapon> PickSWWMSlot5()
|
||||
{
|
||||
return PickPair('Eviscerator','HeavyMahSheenGun');
|
||||
}
|
||||
// rocket launcher spawn
|
||||
static Class<Weapon> PickSWWMSlot6()
|
||||
{
|
||||
return PickPair('Hellblazer','Quadravol');
|
||||
}
|
||||
// first plasma rifle spawn
|
||||
static Class<Weapon> PickSWWMSlot7()
|
||||
{
|
||||
return PickPair('Sparkster','ModernSparkster');
|
||||
}
|
||||
// second plasma rifle spawn
|
||||
static Class<Weapon> PickSWWMSlot8()
|
||||
{
|
||||
return PickPair('SilverBullet','RayKhom');
|
||||
}
|
||||
// first bfg spawn
|
||||
static Class<Weapon> PickSWWMSlot9()
|
||||
{
|
||||
// 25% chance to still drop another candy gun if it's not at max capacity
|
||||
if ( !Random[Replacements](0,3) && ItemExists('CandyGun') && CheckNeedsItem('CandyGunSpares',true) )
|
||||
return 'CandyGun';
|
||||
return PickPair('CandyGun','MisterRifle');
|
||||
}
|
||||
// second bfg spawn (each weapon can only exist once)
|
||||
static Class<Weapon> PickSWWMSlot0( bool fallback = true )
|
||||
{
|
||||
if ( ItemExists('Ynykron',mapstart:true) )
|
||||
{
|
||||
if ( ItemExists('RafanKos',mapstart:true) )
|
||||
return fallback?PickSWWMSlot9():null;
|
||||
return 'RafanKos';
|
||||
}
|
||||
if ( ItemExists('RafanKos',mapstart:true) )
|
||||
return 'Ynykron';
|
||||
return Random[Replacements](0,1)?'Ynykron':'RafanKos';
|
||||
}
|
||||
// either plasma rifle spawn
|
||||
static Class<Weapon> PickDoomSlot6()
|
||||
{
|
||||
bool hasslot7 = (!CheckNeedsItem('Sparkster')||!CheckNeedsItem('ModernSparkster'));
|
||||
bool hasslot8 = (!CheckNeedsItem('SilverBullet')||!CheckNeedsItem('RayKhom'));
|
||||
// if the player already has a slot 7 weapon...
|
||||
if ( hasslot7 )
|
||||
{
|
||||
// ... and also has a slot 8 weapon, 33% chance of a slot 8 spawn
|
||||
// otherwise, guaranteed slot 8 spawn
|
||||
if ( hasslot8 && Random[Replacements](0,2) ) return PickSWWMSlot7();
|
||||
else return PickSWWMSlot8();
|
||||
}
|
||||
// otherwise, always spawn a slot 7 weapon first
|
||||
return PickSWWMSlot7();
|
||||
}
|
||||
// either bfg spawn
|
||||
static Class<Weapon> PickDoomSlot7()
|
||||
{
|
||||
bool hasslot9 = (!CheckNeedsItem('CandyGun')||!CheckNeedsItem('MisterRifle'));
|
||||
bool hasslot0 = (!CheckNeedsItem('Ynykron')||!CheckNeedsItem('RafanKos'));
|
||||
let rep = PickSWWMSlot0(false);
|
||||
// if the player already has a slot 9 weapon (and a slot 0 weapon can still spawn)...
|
||||
if ( hasslot9 && rep )
|
||||
{
|
||||
// ... and also has a slot 0 weapon already, 33% chance of a slot 0 spawn
|
||||
// otherwise, guaranteed slot 0 spawn
|
||||
if ( hasslot0 && Random[Replacements](0,2) ) return PickSWWMSlot9();
|
||||
else return rep;
|
||||
}
|
||||
// otherwise, always spawn a slot 9 weapon first
|
||||
return PickSWWMSlot9();
|
||||
}
|
||||
// either shotgun spawn (also used for Heretic)
|
||||
static Class<Weapon> PickDoomSlot3()
|
||||
{
|
||||
// always slot 3 after map start, prevents shotgun guys from dropping wallbusters, which is weird af
|
||||
if ( level.maptime ) return PickSWWMSlot3();
|
||||
bool hasslot3 = (!CheckNeedsItem('Spreadgun')||!CheckNeedsItem('PuntzerBeta'));
|
||||
bool hasslot4 = (!CheckNeedsItem('Wallbuster')||!CheckNeedsItem('PuntzerGamma'));
|
||||
// if the player already has a slot 3 weapon...
|
||||
if ( hasslot3 )
|
||||
{
|
||||
// ... and also has a slot 4 weapon, 33% chance of a slot 4 spawn
|
||||
// otherwise, guaranteed slot 4 spawn
|
||||
if ( hasslot4 && Random[Replacements](0,2) ) return PickSWWMSlot3();
|
||||
return PickSWWMSlot4();
|
||||
}
|
||||
// otherwise, always spawn a slot 3 weapon first
|
||||
return PickSWWMSlot3();
|
||||
}
|
||||
}
|
||||
622
zscript/utility/swwm_utility_map.zsc
Normal file
622
zscript/utility/swwm_utility_map.zsc
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
// map interaction/info functions
|
||||
enum EExitType
|
||||
{
|
||||
ET_Normal,
|
||||
ET_Secret,
|
||||
ET_EndGame,
|
||||
ET_NewMap,
|
||||
};
|
||||
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// how the fuck is this not available to ZScript?
|
||||
// copied from P_PointOnLineSidePrecise()
|
||||
deprecated("4.11", "Use Level.PointOnLineSide() instead") static clearscope int PointOnLineSide( Vector2 p, Line l )
|
||||
{
|
||||
if ( !l ) return 0;
|
||||
return (((p.y-l.v1.p.y)*l.delta.x+(l.v1.p.x-p.x)*l.delta.y) > double.epsilon);
|
||||
}
|
||||
|
||||
// haha another one
|
||||
// copied from BoxOnLineSide()
|
||||
deprecated("4.11", "Use Level.BoxOnLineSide() instead") static clearscope int BoxOnLineSide( double top, double bottom, double left, double right, Line l )
|
||||
{
|
||||
if ( !l ) return 0;
|
||||
int p1, p2;
|
||||
if ( l.delta.x == 0 )
|
||||
{
|
||||
// ST_VERTICAL:
|
||||
p1 = (right < l.v1.p.x);
|
||||
p2 = (left < l.v1.p.x);
|
||||
if ( l.delta.y < 0 )
|
||||
{
|
||||
p1 ^= 1;
|
||||
p2 ^= 1;
|
||||
}
|
||||
}
|
||||
else if ( l.delta.y == 0 )
|
||||
{
|
||||
// ST_HORIZONTAL:
|
||||
p1 = (top > l.v1.p.y);
|
||||
p2 = (bottom > l.v1.p.y);
|
||||
if ( l.delta.x < 0 )
|
||||
{
|
||||
p1 ^= 1;
|
||||
p2 ^= 1;
|
||||
}
|
||||
}
|
||||
else if ( (l.delta.x*l.delta.y) >= 0 )
|
||||
{
|
||||
// ST_POSITIVE:
|
||||
p1 = PointOnLineSide((left,top),l);
|
||||
p2 = PointOnLineSide((right,bottom),l);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ST_NEGATIVE:
|
||||
p1 = PointOnLineSide((right,top),l);
|
||||
p2 = PointOnLineSide((left,bottom),l);
|
||||
}
|
||||
return (p1==p2)?p1:-1;
|
||||
}
|
||||
|
||||
// wrapper
|
||||
deprecated("4.11", "Use Level.ActorOnLineSide() instead") static clearscope int ActorOnLineSide( Actor a, Line l )
|
||||
{
|
||||
double box[4];
|
||||
box[0] = a.pos.y+a.radius;
|
||||
box[1] = a.pos.y-a.radius;
|
||||
box[2] = a.pos.x-a.radius;
|
||||
box[3] = a.pos.x+a.radius;
|
||||
return BoxOnLineSide(box[0],box[1],box[2],box[3],l);
|
||||
}
|
||||
|
||||
// Thanks to ZZYZX and Nash
|
||||
static play void SetToSlopeSpecific( Actor a, double dang, SecPlane plane, bool flipnorm )
|
||||
{
|
||||
Vector3 fnormal;
|
||||
if ( flipnorm ) fnormal = -plane.Normal;
|
||||
else fnormal = plane.Normal;
|
||||
vector2 fnormalp1 = ((fnormal.x != 0) || (fnormal.y != 0))?(fnormal.x,fnormal.y).Unit():(0,0);
|
||||
vector2 fnormalp2 = ((fnormal.x,fnormal.y).Length(),fnormal.z);
|
||||
double fang = atan2(fnormalp1.y,fnormalp1.x); // floor angle (not pitch!)
|
||||
double fpitch = atan2(fnormalp2.x,fnormalp2.y); // floor pitch
|
||||
double ddiff1 = sin(fang-dang);
|
||||
double ddiff2 = cos(fang-dang);
|
||||
a.pitch = fpitch*ddiff2;
|
||||
a.roll = -fpitch*ddiff1;
|
||||
a.angle = dang;
|
||||
}
|
||||
|
||||
static play void SetToSlope( Actor a, double dang, bool ceil = false )
|
||||
{
|
||||
Sector sect;
|
||||
SecPlane plane;
|
||||
Vector3 fnormal;
|
||||
bool flipnorm;
|
||||
if ( ceil )
|
||||
{
|
||||
sect = a.CeilingSector;
|
||||
plane = sect.ceilingplane;
|
||||
flipnorm = true;
|
||||
fnormal = -sect.ceilingplane.Normal;
|
||||
}
|
||||
else
|
||||
{
|
||||
sect = a.FloorSector;
|
||||
plane = sect.floorplane;
|
||||
flipnorm = false;
|
||||
fnormal = sect.floorplane.Normal;
|
||||
}
|
||||
// find closest 3d floor for its normal
|
||||
F3DFloor ff;
|
||||
for ( int i=0; i<sect.Get3DFloorCount(); i++ )
|
||||
{
|
||||
if ( !(sect.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
||||
if ( !ceil && !(sect.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
|
||||
else if ( ceil && !(sect.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.ceilingz) ) continue;
|
||||
ff = sect.Get3DFloor(i);
|
||||
break;
|
||||
}
|
||||
if ( ff )
|
||||
{
|
||||
if ( ceil )
|
||||
{
|
||||
plane = ff.bottom;
|
||||
flipnorm = false;
|
||||
fnormal = ff.bottom.Normal;
|
||||
}
|
||||
else
|
||||
{
|
||||
plane = ff.top;
|
||||
flipnorm = true;
|
||||
fnormal = -ff.top.Normal;
|
||||
}
|
||||
}
|
||||
SetToSlopeSpecific(a,dang,plane,flipnorm);
|
||||
}
|
||||
|
||||
static clearscope int GetLineLock( Line l )
|
||||
{
|
||||
int locknum = l.locknumber;
|
||||
if ( !locknum )
|
||||
{
|
||||
// check the special
|
||||
switch ( l.special )
|
||||
{
|
||||
case FS_Execute:
|
||||
locknum = l.Args[2];
|
||||
break;
|
||||
case Door_LockedRaise:
|
||||
case Door_Animated:
|
||||
locknum = l.Args[3];
|
||||
break;
|
||||
case ACS_LockedExecute:
|
||||
case ACS_LockedExecuteDoor:
|
||||
case Generic_Door:
|
||||
locknum = l.Args[4];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return locknum;
|
||||
}
|
||||
|
||||
// return if a line is an exit, and additionally the type of exit
|
||||
static clearscope bool, int IsExitLine( Line l )
|
||||
{
|
||||
if ( l.special == Exit_Secret )
|
||||
return true, ET_Secret;
|
||||
if ( l.special == Exit_Normal )
|
||||
return true, ET_Normal;
|
||||
if ( l.special == Teleport_EndGame )
|
||||
return true, ET_EndGame;
|
||||
if ( l.special == Teleport_NewMap )
|
||||
return true, ET_NewMap;
|
||||
// E1M8 compat
|
||||
if ( (l.special == ACS_Execute) && (l.Args[0] == -Int('E1M8_KNOCKOUT')) )
|
||||
return true, ET_Normal;
|
||||
// spooktober™
|
||||
if ( ((l.special == ACS_Execute) || (l.special == ACS_ExecuteAlways)) && (l.Args[0] == -Int('MapFadeOut')) )
|
||||
{
|
||||
if ( level.levelnum == 1 )
|
||||
{
|
||||
let lv = levelinfo.FindLevelByNum(l.Args[2]);
|
||||
if ( lv && lv.mapname.Left(6) ~== "SECRET" )
|
||||
return true, ET_Secret;
|
||||
else return true, ET_NewMap;
|
||||
}
|
||||
return true, ET_Normal;
|
||||
}
|
||||
return false, ET_Normal;
|
||||
}
|
||||
|
||||
static clearscope bool IsTeleportLine( Line l, bool all = false )
|
||||
{
|
||||
// must be two-sided and crossable
|
||||
if ( !l.sidedef[1] || !(l.Activation&(SPAC_Cross|SPAC_MCross|SPAC_PCross|SPAC_AnyCross)) ) return false;
|
||||
// filter lines that aren't player-activated (unless checking all)
|
||||
if ( !all && !(l.Activation&SPAC_PlayerActivate) ) return false;
|
||||
// typical teleports
|
||||
if ( (l.special == Teleport) || (l.special == Teleport_NoStop) )
|
||||
return true;
|
||||
// if checking all, also include sneaky teleports
|
||||
if ( all && ((l.special == Teleport_Line) || (l.special == Teleport_NoFog)) )
|
||||
return true;
|
||||
// exits are included too
|
||||
if ( IsExitLine(l) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static clearscope bool IsValidLockNum( int l )
|
||||
{
|
||||
if ( (l < 1) || (l > 255) ) return true;
|
||||
return SWWMCachedLockInfo.IsValidLock(l);
|
||||
}
|
||||
|
||||
static clearscope Color GetLockColor( int l )
|
||||
{
|
||||
return SWWMCachedLockInfo.GetLockColor(l);
|
||||
}
|
||||
|
||||
static clearscope bool IsDoorSector( Sector s, int part )
|
||||
{
|
||||
// super-easy mode: check for boss special sectors
|
||||
if ( (level.mapname ~== "E1M8") || (level.mapname ~== "E2M8") || (level.mapname ~== "E3M8")
|
||||
|| (level.mapname ~== "E4M6") || (level.mapname ~== "E4M8") || (level.mapname ~== "E5M8")
|
||||
|| (level.mapname ~== "MAP07") )
|
||||
{
|
||||
let si = level.CreateSectorTagIterator(666);
|
||||
int idx;
|
||||
while ( (idx = si.Next()) != -1 )
|
||||
if ( level.Sectors[idx] == s )
|
||||
return true;
|
||||
if ( level.mapname ~== "MAP07" )
|
||||
{
|
||||
let si2 = level.CreateSectorTagIterator(667);
|
||||
while ( (idx = si.Next()) != -1 )
|
||||
if ( level.Sectors[idx] == s )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// moderate: see if it's a busted crusher, we need to be able to break those in case they cause a softlock
|
||||
let ti = ThinkerIterator.Create("SWWMCrusherBroken",Thinker.STAT_USER);
|
||||
SWWMCrusherBroken cb;
|
||||
while ( cb = SWWMCrusherBroken(ti.Next()) )
|
||||
{
|
||||
if ( (part == 0) && (cb.fsec == s) ) return true;
|
||||
if ( (part == 1) && (cb.csec == s) ) return true;
|
||||
}
|
||||
// hard mode: look for all lines/actors with movement specials referencing us
|
||||
foreach ( l:level.Lines )
|
||||
{
|
||||
if ( !l.special ) continue;
|
||||
if ( (part && (l.special >= 10) && (l.special <= 13))
|
||||
|| (!part && (l.special >= 20) && (l.special <= 25))
|
||||
|| (!part && (l.special == 28))
|
||||
|| ((l.special >= 29) && (l.special <= 30))
|
||||
|| (!part && (l.special >= 35) && (l.special <= 37))
|
||||
|| (part && (l.special >= 40) && (l.special <= 45))
|
||||
|| (!part && (l.special == 46))
|
||||
|| (part && (l.special == 47))
|
||||
|| (!part && (l.special >= 60) && (l.special <= 68))
|
||||
|| (part && (l.special == 69))
|
||||
|| ((l.special >= 94) && (l.special <= 96))
|
||||
|| (part && (l.special == 97))
|
||||
|| (!part && (l.special == 99))
|
||||
|| (part && (l.special == 104))
|
||||
|| (part && (l.special >= 105) && (l.special <= 106))
|
||||
|| (part && (l.special >= 168) && (l.special <= 169))
|
||||
|| (!part && (l.special == 172))
|
||||
|| (part && (l.special >= 192) && (l.special <= 199))
|
||||
|| (!part && (l.special == 200))
|
||||
|| (part && (l.special >= 201) && (l.special <= 202))
|
||||
|| (!part && (l.special == 203))
|
||||
|| (part && (l.special == 205))
|
||||
|| (!part && (l.special >= 206) && (l.special <= 207))
|
||||
|| (!part && (l.special == 228))
|
||||
|| (!part && (l.special >= 230) && (l.special <= 231))
|
||||
|| (!part && (l.special >= 238) && (l.special <= 242))
|
||||
|| ((l.special >= 245) && (l.special <= 247))
|
||||
|| (part && (l.special == 249))
|
||||
|| (!part && (l.special >= 250) && (l.special <= 251))
|
||||
|| (part && (l.special >= 251) && (l.special <= 255))
|
||||
|| (!part && (l.special >= 256) && (l.special <= 261))
|
||||
|| (part && (l.special >= 262) && (l.special <= 269))
|
||||
|| (!part && (l.special == 275))
|
||||
|| (part && (l.special == 276))
|
||||
|| (!part && (l.special == 279))
|
||||
|| (part && (l.special == 280)) )
|
||||
{
|
||||
let si = level.CreateSectorTagIterator(l.Args[0],l);
|
||||
int idx;
|
||||
while ( (idx = si.Next()) != -1 )
|
||||
if ( level.Sectors[idx] == s )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ti = ThinkerIterator.Create("Actor");
|
||||
Actor a;
|
||||
while ( a = Actor(ti.Next()) )
|
||||
{
|
||||
if ( !a.special || !a.Args[0] ) continue;
|
||||
if ( (part && (a.special >= 10) && (a.special <= 13))
|
||||
|| (!part && (a.special >= 20) && (a.special <= 25))
|
||||
|| (!part && (a.special == 28))
|
||||
|| ((a.special >= 29) && (a.special <= 30))
|
||||
|| (!part && (a.special >= 35) && (a.special <= 37))
|
||||
|| (part && (a.special >= 40) && (a.special <= 45))
|
||||
|| (!part && (a.special == 46))
|
||||
|| (part && (a.special == 47))
|
||||
|| (!part && (a.special >= 60) && (a.special <= 68))
|
||||
|| (part && (a.special == 69))
|
||||
|| ((a.special >= 94) && (a.special <= 96))
|
||||
|| (part && (a.special == 97))
|
||||
|| (!part && (a.special == 99))
|
||||
|| (part && (a.special == 104))
|
||||
|| (part && (a.special >= 105) && (a.special <= 106))
|
||||
|| (part && (a.special >= 168) && (a.special <= 169))
|
||||
|| (!part && (a.special == 172))
|
||||
|| (part && (a.special >= 192) && (a.special <= 199))
|
||||
|| (!part && (a.special == 200))
|
||||
|| (part && (a.special >= 201) && (a.special <= 202))
|
||||
|| (!part && (a.special == 203))
|
||||
|| (part && (a.special == 205))
|
||||
|| (!part && (a.special >= 206) && (a.special <= 207))
|
||||
|| (!part && (a.special == 228))
|
||||
|| (!part && (a.special >= 230) && (a.special <= 231))
|
||||
|| (!part && (a.special >= 238) && (a.special <= 242))
|
||||
|| ((a.special >= 245) && (a.special <= 247))
|
||||
|| (part && (a.special == 249))
|
||||
|| (!part && (a.special >= 250) && (a.special <= 251))
|
||||
|| (part && (a.special >= 251) && (a.special <= 255))
|
||||
|| (!part && (a.special >= 256) && (a.special <= 261))
|
||||
|| (part && (a.special >= 262) && (a.special <= 269))
|
||||
|| (!part && (a.special == 275))
|
||||
|| (part && (a.special == 276))
|
||||
|| (!part && (a.special == 279))
|
||||
|| (part && (a.special == 280)) )
|
||||
{
|
||||
let si = level.CreateSectorTagIterator(a.Args[0]);
|
||||
int idx;
|
||||
while ( (idx = si.Next()) != -1 )
|
||||
if ( level.Sectors[idx] == s )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// the stupidest thing ever, it's called BlockingLine but it's not always blocking us
|
||||
static play bool BlockingLineIsBlocking( Actor a, int blockflags = Line.ML_BLOCKEVERYTHING, Line testline = null )
|
||||
{
|
||||
Line l = testline?testline:a.BlockingLine;
|
||||
// not blocked
|
||||
if ( !l ) return false;
|
||||
// one-sided always blocking
|
||||
if ( !l.sidedef[1] ) return true;
|
||||
// same for block everything lines
|
||||
if ( l.flags&blockflags ) return true;
|
||||
// lower and upper bounds hit?
|
||||
double afloor = l.frontsector.floorplane.ZAtPoint(a.pos.xy),
|
||||
bfloor = l.backsector.floorplane.ZAtPoint(a.pos.xy),
|
||||
aceil = l.frontsector.ceilingplane.ZAtPoint(a.pos.xy),
|
||||
bceil = l.backsector.ceilingplane.ZAtPoint(a.pos.xy);
|
||||
if ( (min(a.pos.z+a.height,a.ceilingz) > min(aceil,bceil)) || (max(a.pos.z,a.floorz) < max(afloor,bfloor)) )
|
||||
return true;
|
||||
// solid 3d floor bounds hit?
|
||||
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
F3DFloor ff = l.frontsector.Get3DFloor(i);
|
||||
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID)) ) continue;
|
||||
double floor = ff.top.ZAtPoint(a.pos.xy);
|
||||
double ceil = ff.bottom.ZAtPoint(a.pos.xy);
|
||||
if ( (a.pos.z+a.height > ceil) && (a.pos.z < floor) )
|
||||
return true;
|
||||
}
|
||||
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
F3DFloor ff = l.backsector.Get3DFloor(i);
|
||||
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID)) ) continue;
|
||||
double floor = ff.top.ZAtPoint(a.pos.xy);
|
||||
double ceil = ff.bottom.ZAtPoint(a.pos.xy);
|
||||
if ( (a.pos.z+a.height > ceil) && (a.pos.z < floor) )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static play Vector3 UseLinePos( Line l )
|
||||
{
|
||||
Vector3 al, ah, bl, bh;
|
||||
if ( !l.sidedef[1] )
|
||||
{
|
||||
// just the whole line
|
||||
al = (l.v1.p,l.frontsector.floorplane.ZatPoint(l.v1.p));
|
||||
ah = (l.v1.p,l.frontsector.ceilingplane.ZatPoint(l.v1.p));
|
||||
bl = (l.v2.p,l.frontsector.floorplane.ZatPoint(l.v2.p));
|
||||
bh = (l.v2.p,l.frontsector.ceilingplane.ZatPoint(l.v2.p));
|
||||
return (al+ah+bl+bh)*.25;
|
||||
}
|
||||
SecPlane highestfloor, lowestfloor, lowestceiling, highestceiling;
|
||||
if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p))
|
||||
&& (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) )
|
||||
{
|
||||
highestfloor = l.frontsector.floorplane;
|
||||
lowestfloor = l.backsector.floorplane;
|
||||
}
|
||||
else
|
||||
{
|
||||
highestfloor = l.backsector.floorplane;
|
||||
lowestfloor = l.frontsector.floorplane;
|
||||
}
|
||||
if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p))
|
||||
&& (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) )
|
||||
{
|
||||
lowestceiling = l.frontsector.ceilingplane;
|
||||
highestceiling = l.backsector.ceilingplane;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowestceiling = l.backsector.ceilingplane;
|
||||
highestceiling = l.frontsector.ceilingplane;
|
||||
}
|
||||
// try to guess what the part that triggers this is
|
||||
if ( l.Activation&SPAC_Cross )
|
||||
{
|
||||
// pick the "intersection"
|
||||
al = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
|
||||
ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
|
||||
bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
|
||||
bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
|
||||
return (al+ah+bl+bh)*.25;
|
||||
}
|
||||
// check if lower part available
|
||||
al = (l.v1.p,lowestfloor.ZatPoint(l.v1.p));
|
||||
ah = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
|
||||
bl = (l.v2.p,lowestfloor.ZatPoint(l.v2.p));
|
||||
bh = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
|
||||
if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) )
|
||||
return (al+ah+bl+bh)*.25;
|
||||
// check if upper part available
|
||||
al = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
|
||||
ah = (l.v1.p,highestceiling.ZatPoint(l.v1.p));
|
||||
bl = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
|
||||
bh = (l.v2.p,highestceiling.ZatPoint(l.v2.p));
|
||||
if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) )
|
||||
return (al+ah+bl+bh)*.25;
|
||||
// check for 3d floors
|
||||
bool floorfound = false;
|
||||
Vector3 fal, fah, fbl, fbh;
|
||||
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
let ff = l.backsector.Get3DFloor(i);
|
||||
fal = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
|
||||
fah = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
|
||||
fbl = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
|
||||
fbh = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
|
||||
// skip if higher, we'll go with the lowest 3d floor (may not be right, but whatever)
|
||||
if ( floorfound && (fah.z > ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue;
|
||||
al = fal;
|
||||
ah = fah;
|
||||
bl = fbl;
|
||||
bh = fbh;
|
||||
floorfound = true;
|
||||
}
|
||||
if ( floorfound ) return (al+ah+bl+bh)*.25;
|
||||
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
|
||||
{
|
||||
let ff = l.frontsector.Get3DFloor(i);
|
||||
fal = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
|
||||
fah = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
|
||||
fbl = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
|
||||
fbh = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
|
||||
// skip if higher, we'll go with the lowest 3d floor (may not be right, but whatever)
|
||||
if ( floorfound && (fah.z > ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue;
|
||||
al = fal;
|
||||
ah = fah;
|
||||
bl = fbl;
|
||||
bh = fbh;
|
||||
floorfound = true;
|
||||
}
|
||||
if ( floorfound ) return (al+ah+bl+bh)*.25;
|
||||
// check for midtex
|
||||
if ( !l.sidedef[0].GetTexture(1).IsNull() )
|
||||
{
|
||||
double ofs = l.sidedef[0].GetTextureYOffset(1);
|
||||
Vector2 siz = TexMan.GetScaledSize(l.sidedef[0].GetTexture(1));
|
||||
Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[0].GetTexture(1));
|
||||
ofs += tofs.y;
|
||||
ofs *= l.sidedef[0].GetTextureYScale(1);
|
||||
siz.y *= l.sidedef[0].GetTextureYScale(1);
|
||||
if ( l.flags&Line.ML_DONTPEGBOTTOM )
|
||||
{
|
||||
al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs);
|
||||
bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs);
|
||||
ah = al+(0,0,siz.y);
|
||||
bh = bl+(0,0,siz.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs);
|
||||
bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs);
|
||||
al = ah-(0,0,siz.y);
|
||||
bl = bh-(0,0,siz.y);
|
||||
}
|
||||
return (al+ah+bl+bh)*.25;
|
||||
}
|
||||
if ( !l.sidedef[1].GetTexture(1).IsNull() )
|
||||
{
|
||||
double ofs = l.sidedef[1].GetTextureYOffset(1);
|
||||
Vector2 siz = TexMan.GetScaledSize(l.sidedef[1].GetTexture(1));
|
||||
Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[1].GetTexture(1));
|
||||
ofs += tofs.y;
|
||||
ofs *= l.sidedef[1].GetTextureYScale(1);
|
||||
siz.y *= l.sidedef[1].GetTextureYScale(1);
|
||||
if ( l.flags&Line.ML_DONTPEGBOTTOM )
|
||||
{
|
||||
al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs);
|
||||
bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs);
|
||||
ah = al+(0,0,siz.y);
|
||||
bh = bl+(0,0,siz.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs);
|
||||
bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs);
|
||||
al = ah-(0,0,siz.y);
|
||||
bl = bh-(0,0,siz.y);
|
||||
}
|
||||
return (al+ah+bl+bh)*.25;
|
||||
}
|
||||
// just use the intersection
|
||||
al = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
|
||||
ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
|
||||
bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
|
||||
bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
|
||||
return (al+ah+bl+bh)*.25;
|
||||
}
|
||||
|
||||
static bool, TextureID DefaceTexture( TextureID checkme )
|
||||
{
|
||||
String tn = TexMan.GetName(checkme);
|
||||
// special case: alt texture names in Doom 2 In Spain Only
|
||||
if ( (tn ~== "MARBFAC2") || (tn ~== "SP_MAR01") )
|
||||
return true, TexMan.CheckForTexture("defaced_MARBFAC2");
|
||||
if ( (tn ~== "MARBFAC3") || (tn ~== "SP_MAR02"))
|
||||
return true, TexMan.CheckForTexture("defaced_MARBFAC3");
|
||||
if ( (tn ~== "MARBFAC4") || (tn ~== "SP_MAR03") )
|
||||
return true, TexMan.CheckForTexture("defaced_MARBFAC4");
|
||||
if ( (tn ~== "MARBFACE") || (tn ~== "SP_MAR04") )
|
||||
return true, TexMan.CheckForTexture("defaced_MARBFACE");
|
||||
if ( (tn ~== "ZZWOLF2") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF2");
|
||||
if ( (tn ~== "ZZWOLF3") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF3");
|
||||
if ( (tn ~== "ZZWOLF4") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF4");
|
||||
if ( (tn ~== "ZZWOLF6") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF6");
|
||||
if ( (tn ~== "ZZWOLF7") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF7");
|
||||
if ( (tn ~== "ZZWOLF12") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF12");
|
||||
if ( (tn ~== "ZZWOLF13") )
|
||||
return true, TexMan.CheckForTexture("defaced_ZZWOLF13");
|
||||
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();
|
||||
foreach ( l:o.Lines )
|
||||
{
|
||||
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;
|
||||
foreach ( l:o.Lines )
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
398
zscript/utility/swwm_utility_math.zsc
Normal file
398
zscript/utility/swwm_utility_math.zsc
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
// math stuff + gutamatics caching
|
||||
|
||||
Struct SWWMProjectionData
|
||||
{
|
||||
swwm_GM_Matrix wtc;
|
||||
int viewx, viewy, vieww, viewh;
|
||||
}
|
||||
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// gutamatics caching
|
||||
static clearscope void PrepareProjData( out SWWMProjectionData d, Vector3 viewpos, double angle, double pitch, double roll, double fov )
|
||||
{
|
||||
double aspect = Screen.GetAspectRatio();
|
||||
// vertical fov
|
||||
double fovratio = (aspect>=1.3)?1.333333:aspect;
|
||||
double fovy = 2.*atan(tan(clamp(fov,5,170)/2.)/fovratio);
|
||||
// world→clip matrix
|
||||
swwm_GM_Matrix view = swwm_GM_Matrix.view(viewpos,angle,pitch,roll);
|
||||
swwm_GM_Matrix perp = swwm_GM_Matrix.perspective(fovy,aspect,5,65535);
|
||||
d.wtc = perp.multiplyMatrix(view);
|
||||
// screen coord data
|
||||
int sblocks = CVar.FindCVar('screenblocks').GetInt();
|
||||
int viewx, viewy, vieww, viewh;
|
||||
[viewx, viewy, vieww, viewh] = Screen.GetViewWindow();
|
||||
int sh = Screen.GetHeight();
|
||||
int h = sh;
|
||||
if ( sblocks < 10 ) h = (sblocks*sh/10)&~7;
|
||||
int bottom = sh-(h+viewy-((h-viewh)/2));
|
||||
d.viewx = viewx;
|
||||
d.viewy = sh-bottom-h;
|
||||
d.vieww = vieww;
|
||||
d.viewh = h;
|
||||
}
|
||||
|
||||
static clearscope Vector3 ProjectPoint( SWWMProjectionData d, Vector3 worldpos )
|
||||
{
|
||||
return d.wtc.multiplyVector3(worldpos).asVector3();
|
||||
}
|
||||
|
||||
static clearscope Vector2 NDCToViewport( SWWMProjectionData d, Vector3 ndc )
|
||||
{
|
||||
return (d.viewx,d.viewy)+(((ndc.x+1)*d.vieww)/2,((-ndc.y+1)*d.viewh)/2);
|
||||
}
|
||||
|
||||
// checks if a point is inside the viewport
|
||||
static clearscope bool TestScreenBounds( SWWMProjectionData d, Vector2 vpos )
|
||||
{
|
||||
return ((vpos.x == clamp(vpos.x,d.viewx,d.viewx+d.vieww))
|
||||
&& (vpos.y == clamp(vpos.y,d.viewy,d.viewy+d.viewh)));
|
||||
}
|
||||
|
||||
// less code duplication
|
||||
static clearscope void AdjustClean_1( out double x, out double y )
|
||||
{
|
||||
x = (x-160)*CleanXFac_1+(Screen.GetWidth()*.5);
|
||||
y = (y-100)*CleanYFac_1+(Screen.GetHeight()*.5);
|
||||
}
|
||||
static clearscope void AdjustClean_1x( out double x )
|
||||
{
|
||||
x = (x-160)*CleanXFac_1+(Screen.GetWidth()*.5);
|
||||
}
|
||||
static clearscope void AdjustClean_1y( out double y )
|
||||
{
|
||||
y = (y-100)*CleanYFac_1+(Screen.GetHeight()*.5);
|
||||
}
|
||||
|
||||
// Vector/Axis utility functions
|
||||
static clearscope Vector3 Vec3FromAngles( double angle, double pitch )
|
||||
{
|
||||
return (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
||||
}
|
||||
static clearscope Vector3 CircleOffset( Vector3 y, Vector3 z, double angle, double radius )
|
||||
{
|
||||
return y*cos(angle)*radius+z*sin(angle)*radius;
|
||||
}
|
||||
static clearscope Vector3 ConeSpread( Vector3 x, Vector3 y, Vector3 z, double angle, double spread )
|
||||
{
|
||||
return (x+y*cos(angle)*spread+z*sin(angle)*spread).unit();
|
||||
}
|
||||
static clearscope Vector3 RotateVector3( Vector3 v, double angle )
|
||||
{
|
||||
Vector2 v2d = Actor.RotateVector(v.xy,angle);
|
||||
return (v2d.x,v2d.y,v.z);
|
||||
}
|
||||
static clearscope Vector3 AngleToVector3( double angle, double length = 1. )
|
||||
{
|
||||
Vector2 v2d = Actor.AngleToVector(angle,length);
|
||||
return (v2d.x,v2d.y,0);
|
||||
}
|
||||
|
||||
// new GetAxes
|
||||
static clearscope Vector3, Vector3, Vector3 GetAxes( double angle, double pitch, double roll )
|
||||
{
|
||||
Quat r = Quat.FromAngles(angle,pitch,roll);
|
||||
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
|
||||
}
|
||||
|
||||
// included here until Gutamatics updates to use native quaternions
|
||||
static clearscope double, double, double ToAngles( Quat q )
|
||||
{
|
||||
double angle = 0., pitch = 0., roll = 0.;
|
||||
double stest = q.z*q.x-q.w*q.y;
|
||||
double angY = 2.*(q.w*q.z+q.x*q.y);
|
||||
double angX = 1.-2.*(q.y*q.y+q.z*q.z);
|
||||
if ( stest < -.4999995 )
|
||||
{
|
||||
angle = atan2(angY,angX);
|
||||
pitch = 90.;
|
||||
roll = swwm_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w)));
|
||||
}
|
||||
else if ( stest > .4999995 )
|
||||
{
|
||||
angle = atan2(angY,angX);
|
||||
pitch = -90.;
|
||||
roll = swwm_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w)));
|
||||
}
|
||||
else
|
||||
{
|
||||
angle = atan2(angY,angX);
|
||||
pitch = -asin(2.*stest);
|
||||
roll = atan2(2.*(q.w*q.x+q.y*q.z),1.-2.*(q.x*q.x+q.y*q.y));
|
||||
}
|
||||
return angle, pitch, roll;
|
||||
}
|
||||
|
||||
// for aiming and shooting
|
||||
static clearscope Vector3 GetPlayerViewDir( Actor player )
|
||||
{
|
||||
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
||||
return r*(1,0,0);
|
||||
}
|
||||
static play Vector3 GetPlayerAimDir( Actor player )
|
||||
{
|
||||
FTranslatedLineTarget t;
|
||||
double pitch = player.BulletSlope(t);
|
||||
Quat r;
|
||||
if ( !t.linetarget ) r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
||||
else r = Quat.FromAngles(player.angle+player.viewangle,pitch,player.roll+player.viewroll);
|
||||
return r*(1,0,0);
|
||||
}
|
||||
static clearscope Vector3, Vector3, Vector3 GetPlayerAxes( Actor player )
|
||||
{
|
||||
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
||||
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
|
||||
}
|
||||
static play Vector3, Vector3, Vector3 GetPlayerAxesAutoAimed( Actor player )
|
||||
{
|
||||
FTranslatedLineTarget t;
|
||||
double pitch = player.BulletSlope(t);
|
||||
Quat r;
|
||||
if ( !t.linetarget ) r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
||||
else r = Quat.FromAngles(player.angle+player.viewangle,pitch,player.roll+player.viewroll);
|
||||
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
|
||||
}
|
||||
static clearscope Vector3 GetPlayerEye( Actor player )
|
||||
{
|
||||
if ( !player.viewpos )
|
||||
return player.Vec2OffsetZ(0,0,player.player.viewz);
|
||||
if ( player.viewpos.flags&VPSF_ABSOLUTEPOS )
|
||||
return player.viewpos.offset;
|
||||
Vector3 origin = player.Vec2OffsetZ(0,0,player.player.viewz);
|
||||
if ( player.viewpos.flags&VPSF_ABSOLUTEOFFSET )
|
||||
return level.Vec3Offset(origin,player.viewpos.offset);
|
||||
Quat r = Quat.FromAngles(player.angle,player.pitch,player.roll); // viewangles are not used
|
||||
return level.Vec3Offset(origin,r*player.viewpos.offset);
|
||||
}
|
||||
static clearscope Vector3 GetFireOffset( Actor player, double x, double y, double z )
|
||||
{
|
||||
Vector3 origin = GetPlayerEye(player);
|
||||
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
|
||||
return level.Vec3Offset(origin,r*(x,-y,z));
|
||||
}
|
||||
|
||||
// thanks zscript
|
||||
static clearscope double fract( double a )
|
||||
{
|
||||
return a-floor(a);
|
||||
}
|
||||
|
||||
static clearscope double lerp( double a, double b, double theta )
|
||||
{
|
||||
return a*(1.-theta)+b*theta;
|
||||
}
|
||||
static clearscope Vector3 LerpVector3( Vector3 a, Vector3 b, double theta )
|
||||
{
|
||||
return a*(1.-theta)+b*theta;
|
||||
}
|
||||
static clearscope Vector2 LerpVector2( Vector2 a, Vector2 b, double theta )
|
||||
{
|
||||
return a*(1.-theta)+b*theta;
|
||||
}
|
||||
static clearscope Color LerpColor( Color a, Color b, double theta )
|
||||
{
|
||||
Color c = Color(
|
||||
int(a.a*(1.-theta)+b.a*theta),
|
||||
int(a.r*(1.-theta)+b.r*theta),
|
||||
int(a.g*(1.-theta)+b.g*theta),
|
||||
int(a.b*(1.-theta)+b.b*theta)
|
||||
);
|
||||
return c;
|
||||
}
|
||||
|
||||
// this can probably be simplified, but I'm lazy
|
||||
static clearscope Vector3 HSVtoRGB( Vector3 hsv )
|
||||
{
|
||||
Vector3 p;
|
||||
p.x = abs(fract(hsv.x+1.)*6.-3.);
|
||||
p.y = abs(fract(hsv.x+(2./3.))*6.-3.);
|
||||
p.z = abs(fract(hsv.x+(1./3.))*6.-3.);
|
||||
Vector3 p2;
|
||||
p2.x = (1.-hsv.y)+clamp(p.x-1.,0.,1.)*hsv.y;
|
||||
p2.y = (1.-hsv.y)+clamp(p.y-1.,0.,1.)*hsv.y;
|
||||
p2.z = (1.-hsv.y)+clamp(p.z-1.,0.,1.)*hsv.y;
|
||||
return p2*hsv.z;
|
||||
}
|
||||
|
||||
// "fast" exponentiation with integer exponents using loops
|
||||
static clearscope double IntPowF( double base, int exp )
|
||||
{
|
||||
if ( exp < 0 ) return 1./IntPowF(base,-exp);
|
||||
if ( exp == 0 ) return 1.;
|
||||
double rslt = base;
|
||||
for ( int i=0; i<exp; i++ ) rslt *= base;
|
||||
return rslt;
|
||||
}
|
||||
static clearscope int IntPow( int base, int exp )
|
||||
{
|
||||
if ( exp < 0 ) return int(1./IntPow(base,-exp));
|
||||
if ( exp == 0 ) return 1;
|
||||
int rslt = base;
|
||||
for ( int i=0; i<exp; i++ ) rslt *= base;
|
||||
return rslt;
|
||||
}
|
||||
|
||||
// pitch from one actor's eyes to another based on a specific fraction of its height
|
||||
// meant for player view, Actor.PitchTo is recommended for more "general" situations
|
||||
static double PitchTo( Actor a, Actor b, double hfact = 1. )
|
||||
{
|
||||
if ( !a || !b ) return 0;
|
||||
Vector3 thispos = a.player?a.Vec2OffsetZ(0,0,a.player.viewz):a.Vec3Offset(0,0,a.GetCameraHeight()-a.floorclip);
|
||||
Vector3 otherpos = b.Vec3Offset(0,0,b.height*hfact);
|
||||
Vector3 diff = level.Vec3Diff(thispos,otherpos);
|
||||
return -atan2(diff.z,diff.xy.length());
|
||||
}
|
||||
|
||||
// extruded box intersection check, useful when checking things that might be hit along a path
|
||||
static clearscope bool ExtrudeIntersect( Actor a, Actor b, Vector3 range, int steps, int pad = 0 )
|
||||
{
|
||||
if ( steps <= 0 ) return BoxIntersect(a,b,pad:pad);
|
||||
double step = 1./steps;
|
||||
for ( double i=step; i<=1.; i+=step )
|
||||
{
|
||||
if ( BoxIntersect(a,b,range*i,pad) )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// box intersection check, for collision detection
|
||||
static clearscope bool BoxIntersect( Actor a, Actor b, Vector3 ofs = (0,0,0), int pad = 0 )
|
||||
{
|
||||
Vector3 diff = level.Vec3Diff(level.Vec3Offset(a.pos,ofs),b.pos);
|
||||
if ( (abs(diff.x) > (a.radius+b.radius+pad)) || (abs(diff.y) > (a.radius+b.radius+pad)) ) return false;
|
||||
if ( (diff.z > a.height+pad) || (diff.z < -(b.height+pad)) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// sphere intersection check, useful for proximity detection
|
||||
static clearscope bool SphereIntersect( Actor a, Vector3 p, double radius )
|
||||
{
|
||||
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
|
||||
Vector3 amin = ap+(-a.radius,-a.radius,0),
|
||||
amax = ap+(a.radius,a.radius,a.height);
|
||||
double distsq = 0.;
|
||||
if ( p.x < amin.x ) distsq += (amin.x-p.x)*(amin.x-p.x);
|
||||
if ( p.x > amax.x ) distsq += (p.x-amax.x)*(p.x-amax.x);
|
||||
if ( p.y < amin.y ) distsq += (amin.y-p.y)*(amin.y-p.y);
|
||||
if ( p.y > amax.y ) distsq += (p.y-amax.y)*(p.y-amax.y);
|
||||
if ( p.z < amin.z ) distsq += (amin.z-p.z)*(amin.z-p.z);
|
||||
if ( p.z > amax.z ) distsq += (p.z-amax.z)*(p.z-amax.z);
|
||||
return (distsq <= (radius*radius));
|
||||
}
|
||||
|
||||
// hitscan exit point calculation given actor, entry point and direction
|
||||
static clearscope Vector3 TraceExit( Actor a, Vector3 p, Vector3 d )
|
||||
{
|
||||
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
|
||||
Vector3 amin = ap+(-a.radius,-a.radius,0),
|
||||
amax = ap+(a.radius,a.radius,a.height);
|
||||
Vector3 tmax, div = (1./d.x,1./d.y,1./d.z);
|
||||
if ( div.x < 0 ) tmax.x = (amin.x-p.x)*div.x;
|
||||
else tmax.x = (amax.x-p.x)*div.x;
|
||||
if ( div.y < 0 ) tmax.y = (amin.y-p.y)*div.y;
|
||||
else tmax.y = (amax.y-p.y)*div.y;
|
||||
if ( div.z < 0 ) tmax.z = (amin.z-p.z)*div.z;
|
||||
else tmax.z = (amax.z-p.z)*div.z;
|
||||
return level.Vec3Offset(p,min(min(tmax.x,tmax.y),tmax.z)*d);
|
||||
}
|
||||
|
||||
// Liang-Barsky line clipping
|
||||
static clearscope bool, Vector2, Vector2 LiangBarsky( Vector2 minclip, Vector2 maxclip, Vector2 v0, Vector2 v1 )
|
||||
{
|
||||
double t0 = 0., t1 = 1.;
|
||||
double xdelta = v1.x-v0.x;
|
||||
double ydelta = v1.y-v0.y;
|
||||
double p, q, r;
|
||||
for ( int i=0;i<4; i++ )
|
||||
{
|
||||
switch ( i )
|
||||
{
|
||||
case 0:
|
||||
p = -xdelta;
|
||||
q = -(minclip.x-v0.x);
|
||||
break;
|
||||
case 1:
|
||||
p = xdelta;
|
||||
q = (maxclip.x-v0.x);
|
||||
break;
|
||||
case 2:
|
||||
p = -ydelta;
|
||||
q = -(minclip.y-v0.y);
|
||||
break;
|
||||
case 3:
|
||||
p = ydelta;
|
||||
q = (maxclip.y-v0.y);
|
||||
break;
|
||||
}
|
||||
if ( (p == 0.) && (q<0.) ) return false, (0,0), (0,0);
|
||||
if ( p < 0 )
|
||||
{
|
||||
r = q/p;
|
||||
if ( r > t1 ) return false, (0,0), (0,0);
|
||||
else if ( r > t0 ) t0 = r;
|
||||
}
|
||||
else if ( p > 0 )
|
||||
{
|
||||
r = q/p;
|
||||
if ( r < t0 ) return false, (0,0), (0,0);
|
||||
else if ( r < t1 ) t1 = r;
|
||||
}
|
||||
}
|
||||
Vector2 ov0 = v0+(xdelta,ydelta)*t0;
|
||||
Vector2 ov1 = v0+(xdelta,ydelta)*t1;
|
||||
return true, ov0, ov1;
|
||||
}
|
||||
|
||||
static play bool InPlayerFOV( PlayerInfo p, Actor a, double maxdist = 0. )
|
||||
{
|
||||
double vfov = p.fov*.5;
|
||||
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
|
||||
let mo = p.camera;
|
||||
if ( !mo ) return false;
|
||||
Vector3 pp;
|
||||
if ( !a.CheckSight(mo,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false;
|
||||
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
|
||||
else pp = mo.Vec3Offset(0,0,mo.GetCameraHeight());
|
||||
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
|
||||
if ( (abs(sc.x) > hfov) || (abs(sc.y) > vfov) ) return false;
|
||||
if ( (maxdist > 0.) && (sc.z > maxdist) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ui-friendly version without CheckSight call
|
||||
static clearscope bool InPlayerFOVSimple( PlayerInfo p, Actor a, double maxdist = 0. )
|
||||
{
|
||||
double vfov = p.fov*.5;
|
||||
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
|
||||
let mo = p.camera;
|
||||
if ( !mo ) return false;
|
||||
Vector3 pp;
|
||||
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
|
||||
else pp = mo.Vec3Offset(0,0,mo.GetCameraHeight());
|
||||
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
|
||||
if ( (abs(sc.x) > hfov) || (abs(sc.y) > vfov) ) return false;
|
||||
if ( (maxdist > 0.) && (sc.z > maxdist) ) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// calculate angle, pitch and yscale of YBeam based on direction vector and length
|
||||
static clearscope double, double, double CalcYBeam( Vector3 dir, double dist )
|
||||
{
|
||||
if ( level.pixelstretch == 1. )
|
||||
{
|
||||
// ez modo
|
||||
double angle = atan2(dir.y,dir.x);
|
||||
double pitch = asin(-dir.z)+90;
|
||||
return angle, pitch, dist;
|
||||
}
|
||||
dir.z *= level.pixelstretch;
|
||||
double len = dir.length();
|
||||
dir /= len;
|
||||
double angle = atan2(dir.y,dir.x);
|
||||
double pitch = asin(-dir.z)+90;
|
||||
double yscale = dist*len;
|
||||
return angle, pitch, yscale;
|
||||
}
|
||||
}
|
||||
135
zscript/utility/swwm_utility_string.zsc
Normal file
135
zscript/utility/swwm_utility_string.zsc
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// string-related functions
|
||||
extend Class SWWMUtility
|
||||
{
|
||||
// bit ugly, but it works
|
||||
static clearscope void ThousandsStr( out String s, int col = Font.CR_UNDEFINED, String colstr = "" )
|
||||
{
|
||||
if ( (col < Font.CR_UNDEFINED) || (col >= Font.NUM_TEXT_COLORS) )
|
||||
ThrowAbortException("col parameter out of range, use colstr for non-standard font colors.");
|
||||
String nstr = s;
|
||||
s.Truncate(0);
|
||||
int len = nstr.CodePointCount();
|
||||
int t = len;
|
||||
if ( nstr.Left(1) == "-" ) t++;
|
||||
for ( int i=0, pos=0; i<len; i++ )
|
||||
{
|
||||
int ch;
|
||||
[ch, pos] = nstr.GetNextCodePoint(pos);
|
||||
s.AppendCharacter(ch);
|
||||
t = (t-1)%3;
|
||||
if ( (pos < len) && !t )
|
||||
{
|
||||
if ( colstr != "" ) s.AppendFormat("\c[%s],\c-",colstr);
|
||||
else if ( col != Font.CR_UNDEFINED ) s.AppendFormat("\c%c,\c-",0x61+col);
|
||||
else s.AppendCharacter(0x2C);
|
||||
}
|
||||
}
|
||||
}
|
||||
static clearscope String ThousandsNum( int n, int col = Font.CR_UNDEFINED, String colstr = "", int digits = 0 )
|
||||
{
|
||||
String nstr;
|
||||
if ( digits > 0 ) nstr = String.Format("%0*d",digits,n);
|
||||
else nstr = String.Format("%d",n);
|
||||
ThousandsStr(nstr,col,colstr);
|
||||
return nstr;
|
||||
}
|
||||
|
||||
static clearscope void StripColor( out String str )
|
||||
{
|
||||
int len = str.CodePointCount();
|
||||
for ( int i=0, pos=0; i<len; i++ )
|
||||
{
|
||||
int remlen = 0;
|
||||
int cplen = 0;
|
||||
int ch, nxt;
|
||||
[ch, nxt] = str.GetNextCodePoint(pos);
|
||||
if ( ch != 0x1C )
|
||||
{
|
||||
pos = nxt;
|
||||
continue;
|
||||
}
|
||||
remlen++;
|
||||
cplen++;
|
||||
[ch, nxt] = str.GetNextCodePoint(pos+remlen);
|
||||
if ( ch == 0x5B )
|
||||
{
|
||||
int ch2;
|
||||
do
|
||||
{
|
||||
[ch2, nxt] = str.GetNextCodePoint(pos+remlen);
|
||||
remlen += nxt-(pos+remlen);
|
||||
cplen++;
|
||||
}
|
||||
while ( ch2 != 0x5D );
|
||||
}
|
||||
remlen++;
|
||||
str.Remove(pos,remlen);
|
||||
len -= cplen;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
static clearscope String SuperscriptNum( int val )
|
||||
{
|
||||
// unicode is fun
|
||||
static const int digs[] = {0x2070,0x00B9,0x00B2,0x00B3,0x2074,0x2075,0x2076,0x2077,0x2078,0x2079};
|
||||
String str = "";
|
||||
int digits = int(Log10(val));
|
||||
for ( int i=digits; i>=0; i-- )
|
||||
{
|
||||
int d = int(val/IntPow(10,i))%10;
|
||||
str.AppendCharacter(digs[d]);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
static clearscope void ObscureText( out String str, int seed, bool alnum = false )
|
||||
{
|
||||
int len = str.CodePointCount();
|
||||
String newstr = "";
|
||||
for ( int i=0, pos=0; i<len; i++ )
|
||||
{
|
||||
seed = ((seed<<1)*35447+(seed/87));
|
||||
int ch;
|
||||
[ch, pos] = str.GetNextCodePoint(pos);
|
||||
if ( (ch == 0x20) || (ch == 0x09) || (ch == 0x0A) )
|
||||
newstr.AppendCharacter(ch);
|
||||
else if ( alnum )
|
||||
{
|
||||
int sd = abs(seed%36);
|
||||
if ( sd >= 10 ) sd += 7;
|
||||
newstr.AppendCharacter(sd+48);
|
||||
}
|
||||
else newstr.AppendCharacter((abs(seed)%95)+32);
|
||||
}
|
||||
str = newstr;
|
||||
}
|
||||
|
||||
static clearscope void BeautifyClassName( out String str )
|
||||
{
|
||||
String workstr = str;
|
||||
str.Truncate(0);
|
||||
workstr.Replace("_"," ");
|
||||
int len = workstr.CodePointCount();
|
||||
for ( int i=0, pos=0; i<len; i++ )
|
||||
{
|
||||
int cp1;
|
||||
[cp1, pos] = workstr.GetNextCodePoint(pos);
|
||||
str.AppendCharacter(cp1);
|
||||
if ( i < len-1 )
|
||||
{
|
||||
int cp2 = workstr.GetNextCodePoint(pos);
|
||||
// this looks awkward, but I have to also account for non-letter characters
|
||||
// uppercase after lowercase
|
||||
if ( (String.CharUpper(cp1) != cp1) && (String.CharLower(cp2) != cp2) )
|
||||
str.AppendCharacter(0x20);
|
||||
// uppercase after non-letter
|
||||
else if ( (String.CharUpper(cp1) == cp1) && (String.CharLower(cp1) == cp1) && (String.CharLower(cp2) != cp2) )
|
||||
str.AppendCharacter(0x20);
|
||||
// non-letter after lowercase
|
||||
else if ( (String.CharUpper(cp1) != cp1) && (String.CharLower(cp2) == cp2) && (String.CharUpper(cp2) == cp2) )
|
||||
str.AppendCharacter(0x20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue