883 lines
26 KiB
Text
883 lines
26 KiB
Text
// 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;
|
|
|
|
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, oldroll;
|
|
double oldlagangle, oldlagpitch, oldlagroll, oldlagready;
|
|
Vector3 oldlagvel;
|
|
double lagangle, lagpitch, lagroll, 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;
|
|
|
|
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, mirrorcooldown;
|
|
transient SWWMItemTracer itrace;
|
|
transient SWWMMirrorTracer mtrace;
|
|
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, lastkiss;
|
|
|
|
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;
|
|
|
|
int oldtagcolor;
|
|
|
|
transient int magtime;
|
|
SWWMMagItem magitem;
|
|
int magitem_cnt;
|
|
|
|
SWWMShadow myshadow;
|
|
|
|
double bobtime, oldbobtime, oldbob;
|
|
|
|
SWWMHandler hnd;
|
|
|
|
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. )
|
|
{
|
|
double str = CVar.GetCVar('swwm_bumpstrength',player).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 )
|
|
{
|
|
if ( !hnd ) 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);
|
|
}
|
|
|
|
// [4.11] proper 3D/2D weapon bob separation
|
|
/*override Vector3, Vector3 BobWeapon3D( double ticfrac )
|
|
{
|
|
if ( !player || !player.ReadyWeapon || player.ReadyWeapon.bDontBob )
|
|
return (0,0,0), (0,0,0);
|
|
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 froll = SWWMUtility.Lerp(oldroll,roll,ticfrac);
|
|
double flagangle = SWWMUtility.Lerp(oldlagangle,lagangle,ticfrac);
|
|
double flagpitch = SWWMUtility.Lerp(oldlagpitch,lagpitch,ticfrac);
|
|
double flagroll = SWWMUtility.Lerp(oldlagroll,lagroll,ticfrac);
|
|
double diffang = fangle-flagangle;
|
|
double diffpitch = fpitch-flagpitch;
|
|
double diffroll = froll-flagroll;
|
|
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;
|
|
}
|
|
if ( abs(diffroll) > 1. )
|
|
{
|
|
int sgn = (diffroll>0)?1:-1;
|
|
diffroll = abs(diffroll)**.7*sgn;
|
|
}
|
|
Vector3 flagvel = SWWMUtility.LerpVector3(oldlagvel,lagvel,ticfrac);
|
|
let [x, y, z] = SWWMUtility.GetAxes(flagangle,flagpitch,flagroll);
|
|
let [x2, y2, z2] = SWWMUtility.GetAxes(flagangle,0,flagroll);
|
|
double diffx = flagvel dot x;
|
|
double diffy = flagvel dot y;
|
|
double diffz = flagvel dot z;
|
|
double diffy2 = flagvel dot y2;
|
|
double diffz2 = flagvel dot z2;
|
|
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;
|
|
}
|
|
if ( abs(diffz) > 1. )
|
|
{
|
|
int sgn = (diffz>0)?1:-1;
|
|
diffz = abs(diffz)**.5*sgn;
|
|
}
|
|
if ( abs(diffy2) > 1. )
|
|
{
|
|
int sgn = (diffy2>0)?1:-1;
|
|
diffy2 = abs(diffy2)**.5*sgn;
|
|
}
|
|
if ( abs(diffz2) > 1. )
|
|
{
|
|
int sgn = (diffz2>0)?1:-1;
|
|
diffz2 = abs(diffz2)**.5*sgn;
|
|
}
|
|
Vector3 bobvec = (diffy,-diffz,diffx)*20. + (cur.x,-cur.y,0)*10.;
|
|
Vector3 bobang = (diffang,diffpitch,diffroll)*.1 - (cur.x,cur.y,0)*.04 - (diffy2,diffz2,0)*.2;
|
|
double fready = SWWMUtility.Lerp(oldlagready,lagready,ticfrac);
|
|
return bobvec*fready, bobang*fready;
|
|
}
|
|
|
|
// compatibility with 2D weapons
|
|
override Vector2 BobWeapon( double ticfrac )
|
|
{
|
|
if ( !player || !player.ReadyWeapon || player.ReadyWeapon.bDontBob )
|
|
return (0,0);
|
|
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;
|
|
}
|
|
cur.x += diffang*.4;
|
|
cur.y -= diffpitch*.4;
|
|
Vector3 flagvel = SWWMUtility.LerpVector3(oldlagvel,lagvel,ticfrac);
|
|
double flagroll = SWWMUtility.Lerp(oldlagroll,lagroll,ticfrac);
|
|
let [x, y, z] = SWWMUtility.GetAxes(flagangle,0,flagroll);
|
|
double diffy = flagvel dot y;
|
|
double diffz = flagvel dot z;
|
|
if ( abs(diffy) > 1. )
|
|
{
|
|
int sgn = (diffy>0)?1:-1;
|
|
diffy = abs(diffy)**.5*sgn;
|
|
}
|
|
if ( abs(diffz) > 1. )
|
|
{
|
|
int sgn = (diffz>0)?1:-1;
|
|
diffz = abs(diffz)**.5*sgn;
|
|
}
|
|
cur.x -= diffy*.8;
|
|
cur.y += diffz*.8;
|
|
return cur*SWWMUtility.Lerp(oldlagready,lagready,ticfrac);
|
|
}*/
|
|
|
|
override Vector2 BobWeapon( double ticfrac )
|
|
{
|
|
if ( !player || !player.ReadyWeapon || player.ReadyWeapon.bDontBob )
|
|
return (0,0);
|
|
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;
|
|
}
|
|
cur.x += diffang*.4;
|
|
cur.y -= diffpitch*.4;
|
|
Vector3 flagvel = SWWMUtility.LerpVector3(oldlagvel,lagvel,ticfrac);
|
|
double flagroll = SWWMUtility.Lerp(oldlagroll,lagroll,ticfrac);
|
|
Vector3 x, y, z;
|
|
[x, y, z] = SWWMUtility.GetAxes(flagangle,0,flagroll);
|
|
double diffy = flagvel dot y;
|
|
double diffz = flagvel dot z;
|
|
if ( abs(diffy) > 1. )
|
|
{
|
|
int sgn = (diffy>0)?1:-1;
|
|
diffy = abs(diffy)**.5*sgn;
|
|
}
|
|
if ( abs(diffz) > 1. )
|
|
{
|
|
int sgn = (diffz>0)?1:-1;
|
|
diffz = abs(diffz)**.5*sgn;
|
|
}
|
|
cur.x -= diffy*.8;
|
|
cur.y += diffz*.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)
|
|
if ( !hnd ) 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)
|
|
if ( !hnd ) 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;
|
|
oldlagroll = lagroll = oldroll = roll;
|
|
}
|
|
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/buttslam1");
|
|
MarkSound("demolitionist/buttslam2");
|
|
MarkSound("demolitionist/buttslam3");
|
|
MarkSound("demolitionist/buttslamx");
|
|
MarkSound("demolitionist/petting");
|
|
MarkSound("demolitionist/knockout");
|
|
}
|
|
}
|