swwmgz_m/zscript/swwm_player.zsc
Marisa Kirisame faddb088c1 Hellblazer fully implemented.
Rebalanced Candygun.
Added vanilla boss healthbars + vanilla boss boosting (both optional).
Adjustments so the E1M8 exit and similar work fine with this mod.
Various language changes (mostly vanilla monster tags).
Doubled damage of Pusher altfire, so it matches the projectile's.
Adjusted minimum fall speed for stomping.
Other small fixed and adjustments.
2020-04-20 21:42:43 +02:00

1780 lines
52 KiB
Text

// The Demolitionist
Class Demolitionist : PlayerPawn
{
int last_jump_held, last_boost, last_kick;
Vector3 dashdir;
double dashfuel, dashboost;
int dashcooldown, boostcooldown, fuelcooldown;
bool dashsnd;
bool sendtoground;
bool key_reentrant;
int lastdamage;
transient int lastdamagetic;
bool lastground;
int lastgroundtic;
double lastvelz, prevvelz;
double ssup;
transient CVar myvoice, mute, fly6dof;
SWWMStats mystats;
int cairtime;
int lastmpain;
double guideangle, guidepitch, guideroll;
// for weapon bobbing stuff
bool bumpdown;
double bumpvelz;
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;
int deadtimer;
transient int revivefail;
Actor selflight;
Default
{
Speed 1;
Radius 16;
Height 56;
Mass 500;
PainChance 255;
Player.DisplayName "Demolitionist";
Player.StartItem "ExplodiumGun";
Player.StartItem "DeepImpact";
Player.ViewHeight 52;
Player.AirCapacity 0;
Player.GruntSpeed 20;
Player.SoundClass "demolitionist";
DamageFactor "Drowning", 0.0;
DamageFactor "Poison", 0.0;
DamageFactor "PoisonCloud", 0.0;
DamageFactor "Falling", 0.0;
+NOBLOOD;
+DONTGIB;
+NOICEDEATH;
}
// oh yay, more cheat modification
override void CheatGive( String name, int amount )
{
if ( !player.mo || (player.health <= 0) ) return;
int giveall = ALL_NO;
if ( name ~== "all" ) giveall = ALL_YES;
else if ( name ~== "everything" ) giveall = ALL_YESYES;
if ( giveall || (name ~== "health") )
{
if ( amount > 0 )
{
health = min(health+amount,1000);
player.health = health;
}
else player.health = health = 1000;
}
if ( giveall || (name ~== "backpack") )
{
// Select the correct type of backpack based on the game
let type = (class<Inventory>)(gameinfo.backpacktype);
let def = GetDefaultByType(type);
if ( type ) GiveInventory(type,def.MaxAmount,true);
if ( !giveall ) return;
}
if ( giveall || (name ~== "ammo") )
{
// Find every unique type of ammo. Give it to the player if
// he doesn't have it already, and set each to its maximum.
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Ammo>)(AllActorClasses[i]);
if ( !type || (type.GetParentClass() != "Ammo") )
continue;
// Only give if it's for a valid weapon, unless using "give everything"
bool isvalid = false;
for ( int j=0; j<AllActorClasses.Size(); j++ )
{
let type2 = (class<Weapon>)(AllActorClasses[j]);
if ( !type2 ) continue;
let rep = GetReplacement(type2);
if ( (rep != type2) && !(rep is "DehackedPickup") ) continue;
readonly<Weapon> weap = GetDefaultByType(type2);
if ( !player.weapons.LocateWeapon(type2) || (weap.bCheatNotWeapon && (giveall != ALL_YESYES)) ) continue;
if ( (type2 is 'SWWMWeapon') && SWWMWeapon(weap).UsesAmmo(type) )
{
isvalid = true;
break;
}
if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) )
{
isvalid = true;
break;
}
}
if ( !isvalid ) continue;
let ammoitem = FindInventory(type);
if ( !ammoitem )
{
ammoitem = Inventory(Spawn(type));
ammoitem.AttachToOwner(self);
ammoitem.Amount = ammoitem.MaxAmount;
}
else if ( ammoitem.Amount < ammoitem.MaxAmount )
ammoitem.Amount = ammoitem.MaxAmount;
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "armor") )
{
// Give only subclasses of SWWMArmor
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (Class<SWWMArmor>)(AllActorClasses[i]);
if ( !type || (type == 'SWWMArmor') ) continue;
if ( GetReplacement(type) == type )
{
let item = Inventory(Spawn(type));
item.ClearCounters(); // don't increase item counts
item.Amount = item.MaxAmount;
if ( !item.CallTryPickup(self) ) item.Destroy();
}
}
// Also give spares
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (Class<SWWMSpareArmor>)(AllActorClasses[i]);
if ( !type || (type == 'SWWMSpareArmor') ) continue;
if ( GetReplacement(type) == type )
{
let item = Inventory(Spawn(type));
item.ClearCounters(); // don't increase item counts
item.Amount = item.MaxAmount;
if ( !item.CallTryPickup(self) ) item.Destroy();
}
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "keys") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
if ( !(AllActorClasses[i] is "Key") ) continue;
// clean up redundant keys
if ( !(gameinfo.gametype&GAME_CHEX) && ((AllActorClasses[i] is 'ChexRedCard') || (AllActorClasses[i] is 'ChexBlueCard') || (AllActorClasses[i] is 'ChexYellowCard')) ) continue;
else if ( (gameinfo.gametype&GAME_CHEX) && !(AllActorClasses[i] is 'ChexRedCard') && !(AllActorClasses[i] is 'ChexBlueCard') && !(AllActorClasses[i] is 'ChexYellowCard') ) continue;
// don't give heretic keys in doom
if ( (gameinfo.gametype&GAME_DOOMCHEX) && ((AllActorClasses[i] is 'KeyYellow') || (AllActorClasses[i] is 'KeyGreen') || (AllActorClasses[i] is 'KeyBlue')) ) continue;
let keyitem = GetDefaultByType(AllActorClasses[i]);
if ( keyitem.special1 )
{
let item = Inventory(Spawn(AllActorClasses[i]));
SWWMHandler.KeyTagFix(item);
if ( !item.CallTryPickup(self) ) item.Destroy();
}
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "weapons") )
{
let savedpending = player.PendingWeapon;
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Weapon>)(AllActorClasses[i]);
if ( !type || (type == "Weapon") ) 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 ( (giveall == ALL_YESYES) || !def.bCheatNotWeapon )
GiveInventory(type,1,true);
}
}
player.PendingWeapon = savedpending;
if ( !giveall ) return;
}
if ( giveall || (name ~== "artifacts") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Inventory>)(AllActorClasses[i]);
if ( !type ) continue;
if ( type is 'Chancebox' ) continue; // do not give these until they're fully implemented
if ( !(gameinfo.gametype&GAME_HEXEN) && (type is 'AmmoFabricator') ) continue; // no fabricators outside of hexen
let def = GetDefaultByType (type);
if ( def.Icon.isValid() &&
!(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") && !(type is "Armor") && !(type is "Key") )
{
// Do not give replaced items unless using "give everything"
if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "puzzlepieces") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<PuzzleItem>)(AllActorClasses[i]);
if ( !type ) continue;
let def = GetDefaultByType(type);
if ( !def.Icon.isValid() ) continue;
// Do not give replaced items unless using "give everything"
if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
if ( !giveall ) return;
}
if ( giveall ) return;
let type = name;
if ( !type )
{
if ( PlayerNumber() == consoleplayer )
A_Log(String.Format("Unknown item \"%s\"\n",name));
}
else GiveInventory(type,amount,true);
}
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( mod == 'Jump' ) return StringTable.Localize("$O_JUMP");
if ( mod == 'Dash' ) return StringTable.Localize("$O_DASH");
if ( mod == 'GroundPound' ) return StringTable.Localize("$O_POUND");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override void GiveDefaultInventory()
{
Super.GiveDefaultInventory();
// preloaded gun
let dp = DeepImpact(FindInventory("DeepImpact"));
if ( dp ) SWWMLoreLibrary.Add(player,"DeepImpact");
let eg = ExplodiumGun(FindInventory("ExplodiumGun"));
if ( !eg ) return;
SWWMLoreLibrary.Add(player,"ExplodiumGun");
eg.clipcount = 7;
eg.chambered = true;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
mystats = SWWMStats.Find(player);
lastground = true;
}
void A_Dash()
{
vel += dashdir*dashboost;
player.vel = vel.xy;
if ( dashboost < 0.1 ) dashboost = 0.;
else
{
A_AlertMonsters(800);
dashboost *= .5;
}
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
dashfuel = max(0.,dashfuel-dashboost);
dashcooldown = 40;
fuelcooldown = max(60,fuelcooldown);
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
SetStateLabel("DashEnd");
}
void A_BoostUp( bool initial = false )
{
vel += (0,0,1)*dashboost;
player.vel = vel.xy;
if ( dashboost < 0.1 ) dashboost = 0.;
else
{
A_AlertMonsters(800);
dashboost *= (player.cmd.buttons&BT_JUMP)?.9:.4;
last_boost = level.maptime+1;
}
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
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 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 ) // 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) && (ff.alpha == 0)) ) 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;
}
}
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_LOOPING|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 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 = Super.BobWeapon(ticfrac);
if ( !oldbob ) player.WeaponState &= ~WF_WEAPONBOBBING;
double fangle = oldangle*(1.-ticfrac)+angle*ticfrac;
double fpitch = (oldpitch*(1.-ticfrac)+pitch*ticfrac);
double flagangle = (oldlagangle*(1.-ticfrac)+lagangle*ticfrac);
double flagpitch = (oldlagpitch*(1.-ticfrac)+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 = oldlagvel*(1.-ticfrac)+lagvel*ticfrac;
double diffx = flagvel dot (cos(flagangle+90),sin(flagangle+90),0);
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;
}
// don't do inertial sway when in 6dof mode, causes issues
if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',players[consoleplayer]);
if ( !fly6dof.GetBool() || !((waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2)) )
{
cur.x += diffang;
cur.y -= diffpitch;
cur.x += diffx*4.;
cur.y += diffy*4.;
}
return cur*(oldlagready*(1.-ticfrac)+lagready*ticfrac);
}
override void PlayerThink()
{
oldangle = angle;
oldpitch = pitch;
Super.PlayerThink();
oldlagangle = lagangle;
oldlagpitch = lagpitch;
lagangle = lagangle*.8+angle*.2;
lagpitch = lagpitch*.8+pitch*.2;
}
override void Tick()
{
Super.Tick();
if ( !player ) return;
if ( !selflight )
{
selflight = Spawn("DemolitionistSelfLight",pos);
selflight.target = self;
selflight.Tick();
}
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(prev,pos).length();
if ( waterlevel < 2 )
{
if ( !player.onground || bNoGravity )
{
cairtime++;
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
mystats.airdist += traveldist;
}
else
{
cairtime = 0;
mystats.grounddist += traveldist;
}
}
CheckUnderwaterAmb();
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) && (health > 0) )
{
// bump down weapon
bumpdown = true;
bumpvelz = -lastvelz;
if ( lastvelz < -25 )
{
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = int(-lastvelz);
ReactionTime = 17;
A_Stop();
A_AlertMonsters();
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 ( (player == players[consoleplayer]) && (lastvelz < -gruntspeed) && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( lastvelz < -1 )
A_Footstep(0,true,clamp(-lastvelz*0.05,0.0,1.0));
}
if ( bumpdown )
{
lagvel.z += bumpvelz*.2;
bumpvelz *= .8;
if ( bumpvelz < double.epsilon ) bumpdown = false;
}
lastground = player.onground;
lastvelz = prevvelz;
prevvelz = vel.z;
bNOFRICTION = (((waterlevel<2)&&(bFly||bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||InStateSequence(CurState,FindState("Dash")));
fuelcooldown = max(0,fuelcooldown-1);
dashcooldown = max(0,dashcooldown-1);
boostcooldown = max(0,boostcooldown-1);
if ( fuelcooldown <= 0 )
dashfuel = min(120.,dashfuel+max(dashfuel*.01,.05));
if ( (dashboost > 0.) && (InStateSequence(CurState,FindState("Dash")) || (InStateSequence(CurState,FindState("Jump")) && player.cmd.buttons&BT_JUMP)) )
dashsnd = true;
else
{
if ( dashsnd )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
dashsnd = false;
}
PainChance = InStateSequence(CurState,FindState("Dash"))?0:255;
if ( dashboost <= 0. ) return;
if ( InStateSequence(CurState,FindState("Dash")) )
{
Actor a;
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.target = self;
a.vel = (RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
Vector3 dir = vel+dashdir*dashboost;
double spd = dir.length();
dir = dir.unit();
// look for things we could potentially bump into
let bi = BlockThingsIterator.Create(self,200);
bool bumped = false;
while ( (spd > 0) && bi.Next() )
{
a = bi.Thing;
if ( !a || (a == self) || !a.bSHOOTABLE || a.bTHRUACTORS ) continue;
Vector3 diff = level.Vec3Diff(level.Vec3Offset(pos,vel),level.Vec3Offset(a.pos,a.vel));
if ( (abs(diff.x) > (radius+a.radius+8)) || (abs(diff.y) > (radius+a.radius+8)) ) continue;
if ( (diff.z > (height+8)) || (diff.z < (8-a.height)) ) continue;
double moveang = atan2(dir.y,dir.x);
Vector3 sc = level.SphericalCoords(pos,a.pos,(moveang,0));
if ( abs(sc.x) > 60 ) continue;
// large monsters will stop the player
A_QuakeEx(1,1,1,3,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
a.A_StartSound("demolitionist/bump",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( a.bDONTTHRUST || (a.Mass >= Mass*5) )
{
if ( bumped ) continue;
bumped = true;
A_QuakeEx(3,3,3,8,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
A_StartSound("demolitionist/bump",CHAN_FOOTSTEP,CHANF_OVERLAP);
vel -= dir*(9+(spd*30/mass));
vel.z += 3+(spd*(20/mass));
}
Vector3 pushdir = Vec3To(a).unit()*.1+dir*.9;
if ( !a.bDONTTHRUST && (a.Mass < Mass*5) )
{
a.vel += pushdir*(15+(spd*150/max(50,a.mass)));
a.vel.z += 5+(spd*(50/max(50,a.mass)));
}
int flg = DMG_THRUSTLESS;
if ( FindInventory("RagekitPower") ) flg |= DMG_FOILINVUL;
a.DamageMobj(self,self,int(15+spd*2.5),'Dash',flg);
}
}
else if ( InStateSequence(CurState,FindState("Jump")) )
{
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.target = self;
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// lucky collar
if ( Health < 25 ) damage /= 4;
if ( source == self ) damage /= 2;
if ( !inflictor && !source && (CurSector.flags&Sector.SECF_ENDLEVEL) )
{
// end level hax
damage = max(50,health-100);
flags |= DMG_FORCED|DMG_NO_ARMOR;
mod = 'EndLevel';
}
lastdamage = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
lastdamagetic = max(lastdamagetic,gametic+clamp(lastdamage/2,10,40));
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
{
lastmpain = level.maptime;
A_DemoPain();
}
return lastdamage;
}
override void CalcHeight()
{
Super.CalcHeight();
// handle smooth step down (hacky but looks ok)
player.viewz += ssup;
ssup = max(0,(ssup*.7)-.25);
}
override void CheckPitch()
{
if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) )
return; // handled in moveplayer
Super.CheckPitch();
}
override void CheckCrouch( bool totallyfrozen )
{
// crouch to swim/float down
if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) )
{
double mult = (player.cmd.buttons&BT_SPEED)?2.:1.;
if ( waterlevel >= 2 ) vel.z = -4*Speed*mult;
else if ( bNOGRAVITY ) vel.z = -3*mult;
}
Super.CheckCrouch(totallyfrozen);
}
override void CheckMoveUpDown()
{
if ( InStateSequence(CurState,FindState("Dash")) )
player.cmd.upmove = 0;
if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) )
{
double fs = TweakSpeeds(1.,0.);
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,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/TICRATE;
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();
}
override void MovePlayer()
{
if ( InStateSequence(CurState,FindState("Dash")) )
player.cmd.forwardmove = player.cmd.sidemove = 0;
if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) )
{
player.onground = false;
if ( player.turnticks )
{
player.turnticks--;
guideangle = (180./TURN180_TICKS);
}
else guideangle += .2*player.cmd.yaw*(360./65536.);
guidepitch -= .2*player.cmd.pitch*(360./65536.);
if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',players[consoleplayer]);
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
if ( player.centering || !fly6dof.GetBool() ) guideroll = clamp(deltaangle(roll,0),-3.,3.);
if ( fly6dof.GetBool() )
{
swwm_Quat orient = swwm_Quat.create_euler(pitch,angle,roll);
swwm_Quat angles = swwm_Quat.create_euler(guidepitch,guideangle,guideroll);
orient = orient.qmul(angles);
double npitch, nangle, nroll;
[npitch, nangle, nroll] = orient.to_euler();
angle = nangle;
pitch = npitch;
roll = nroll;
}
else
{
angle += guideangle;
pitch += guidepitch;
pitch = clamp(pitch,player.MinPitch,player.MaxPitch);
roll += guideroll;
}
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
{
guideroll = guidepitch = roll = pitch = 0.;
player.centering = false;
}
double fs = TweakSpeeds(1.,0.);
double jcmove = 0.;
if ( player.cmd.buttons&BT_JUMP ) jcmove += 4096.;
if ( player.cmd.buttons&BT_CROUCH ) jcmove -= 4096.;
if ( player.cmd.buttons&BT_SPEED ) jcmove *= 2.;
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 accel = x*player.cmd.forwardmove+y*player.cmd.sidemove+z*jcmove;
accel *= fs/320.;
double spd = vel.length();
if ( spd > 40. ) vel = (vel+accel/TICRATE).unit()*spd;
else vel = vel+accel/TICRATE;
vel *= .97;
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
{
Super.MovePlayer();
// override air control because we REALLY need the extra mobility
if ( !player.onground && !bNOGRAVITY && (waterlevel < 2) )
{
double fs = TweakSpeeds(1.,0.);
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 accel = (RotateVector((player.cmd.forwardmove,-player.cmd.sidemove),angle),0);
accel *= fs/960.;
double spd = vel.length();
if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd;
else vel = vel+accel/TICRATE;
}
if ( abs(roll) > 0. ) roll += clamp(deltaangle(roll,0),-3.,3.);
}
guideangle *= .9;
guidepitch *= .9;
guideroll *= .9;
// anchor to ground when going down steps
if ( lastground && !player.onground && !bFly && !bFlyCheat && (waterlevel < 2) && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z < 0) )
{
ssup = max(0,(pos.z-floorz));
SetOrigin(Vec2OffsetZ(0,0,floorz),true);
lastground = player.onground = true;
}
if ( player.onground ) lastgroundtic = gametic;
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
PlayIdle();
Vector3 dodge = (0,0,0), x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,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.onground ) dodge.z = max(0,dodge.z);
if ( dodge == (0,0,0) ) dodge.xy = RotateVector((1,0),angle);
if ( (waterlevel < 2) && !(bFly || bFlyCheat) )
dodge.z += .15;
if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && level.IsJumpingAllowed() )
{
dashdir = dodge.unit();
dashcooldown = 40;
dashboost = 20.;
bOnMobj = false;
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
vel.z += clamp(-vel.z*.4,0.,30.);
player.jumptics = -1;
SetStateLabel("Dash");
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
mystats.dashcount++;
}
}
override void CheckJump()
{
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
Vector2 walldir = (cos(angle),sin(angle));
bool walljump = false, wallclimb = false;
FLineTraceData d;
Actor jumpactor = null;
for ( int i=-4; i<int(height*.8); i++ )
{
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 )
{
jumpactor = d.HitActor;
walldir = (walldir*.7+(cos(ang),sin(ang))*.3);
break;
}
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
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+(cos(ang+180),sin(ang+180))*.3);
break;
}
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( player.cmd.buttons&BT_JUMP )
{
double mult = (player.cmd.buttons&BT_SPEED)?2.:1.;
if ( player.crouchoffset ) player.crouching = 1;
else if ( waterlevel >= 2 ) vel.z = 4*Speed*mult;
else if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) ) return;
else if ( bNoGravity ) vel.z = 3*mult;
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./TICRATE;
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;
double pvelz = vel.z;
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
{
// check for wall stuff
if ( walljump )
{
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.6,0.,30.);
vel.xy += walldir*20*Speed;
}
else if ( wallclimb )
{
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.8,0.,30.);
vel.xy += walldir*5*Speed;
}
if ( jumpactor && jumpactor.bSHOOTABLE )
{
SWWMHandler.DoKnockback(jumpactor,(-walldir,0),12000);
jumpactor.DamageMobj(self,self,10,'Jump');
}
if ( walljump || wallclimb )
{
A_StartSound("demolitionist/kick",CHAN_FOOTSTEP,CHANF_OVERLAP);
last_kick = level.maptime+1;
}
}
bOnMobj = false;
player.jumpTics = -1;
if ( !(player.cheats&CF_PREDICTING) )
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
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);
mystats.boostcount++;
last_boost = level.maptime+1;
}
else
{
dashboost = 0.;
if ( vel.z < 10. )
vel.z += jumpvelz*1.25;
}
SetStateLabel("Jump");
}
last_jump_held = level.maptime+1;
}
}
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 pitch += dpitch;
// add roll
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
if ( abs(droll) < 2. ) roll = 50.;
else roll += droll;
player.mo.CalcHeight();
if ( player.damagecount ) player.damagecount--;
if ( player.poisoncount ) player.poisoncount--;
if ( player.viewheight <= 6 )
{
deadtimer++;
if ( deadtimer == 60 )
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI|CHANF_LOCAL);
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (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) && swwm_revive )
{
// reboot (if possible)
if ( !FindInventory("ReviveCooldown") )
{
player.Resurrect();
roll = 0;
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = 30;
ReactionTime = 17;
A_Stop();
A_AlertMonsters();
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);
}
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 )
GiveInventory("ReviveCooldown",1);
}
else if ( level.maptime > revivefail )
{
A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI|CHANF_LOCAL);
revivefail = level.maptime+120;
}
}
}
else deadtimer = 0;
}
override void PlayIdle()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("Spawn")) )
SetStateLabel("Spawn");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("Crouch");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("FloatLoop")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("FloatLoop")) )
{
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 ( !bNoGravity && (waterlevel < 1) )
{
// Falling
if ( (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("FloatLoop")))
&& (abs(pos.z-floorz) > maxstepheight) )
SetStateLabel("Fall");
}
else
{
// Floating
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("FloatLoop")) )
SetStateLabel("Fall");
}
}
override void PlayRunning()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("See")) )
SetStateLabel("See");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
SetStateLabel("CrouchMove");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( player.cmd.buttons&BT_SPEED
&& (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))) )
SetStateLabel("SeeFast");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn")) )
{
SetStateLabel("See");
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
}
}
}
else if ( !bNoGravity && (waterlevel < 1) )
{
// Falling
PlayIdle();
}
else
{
// Floating
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| 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");
}
}
override void PlayAttacking()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
override void PlayAttacking2()
{
PlayAttacking();
}
void A_DMFade()
{
if ( !deathmatch || player ) return;
A_FadeOut(0.03);
}
void A_DemoPain()
{
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
if ( lastdamage > 90 )
{
A_StartSound("*pain25",CHAN_VOICE);
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
else if ( lastdamage > 30 )
{
A_StartSound("*pain50",CHAN_VOICE);
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
else if ( lastdamage > 0 )
{
A_StartSound("*pain100",CHAN_VOICE);
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
}
void A_DemoScream()
{
A_StopSound(CHAN_DEMOVOICE);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
A_PlayerScream();
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
override bool OnGiveSecret( bool printmsg, bool playsound )
{
if ( !player ) return false;
int score = 500;
// last secret (this is called before counting it up, so have to subtract)
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
{
if ( hnd ) hnd.allsecrets = true;
score = 5000;
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),player.GetUserName(),score);
}
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score);
if ( CheckLocalView() ) SWWMHandler.AddOneliner("findsecret",2,40);
SWWMCredits.Give(player,score);
return true;
}
override void AddInventory( Inventory item )
{
Super.AddInventory(item);
if ( !player ) return;
// add lore if any
SWWMLoreLibrary.Add(player,item.GetClassName());
if ( (item is 'Weapon') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() )
SWWMHandler.AddOneliner("getweapon",2);
if ( (item is 'Key') && !key_reentrant )
{
// score
int score = 250;
Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score);
SWWMCredits.Give(player,score);
if ( !swwm_sharekeys ) return;
// 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;
players[i].mo.GiveInventory(item.GetClass(),1);
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = false;
}
}
}
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') )
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;
}
void A_Footstep( double yofs, bool run = false, double vol = .3 )
{
if ( run )
{
A_StartSound("demolitionist/run",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.A_CheckTerrain();
}
else
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
let b = Spawn("SmolInvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.A_CheckTerrain();
}
}
override bool Used( Actor user )
{
if ( !(user is 'Demolitionist') || !user.player ) return false;
if ( (user.player == players[consoleplayer]) && (health > 0) )
{
SWWMHandler.AddOneliner("greet",2);
return true;
}
return false;
}
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_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"SeeFast");
XZW1 E 0 A_Footstep(1);
XZW1 EFGHIJKL 2 A_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"SeeFast");
XZW1 M 0 A_Footstep(-1);
XZW1 MNOPA 2 A_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"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;
SeeFast:
// sprinting
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
XZW1 QRST 2;
Goto SeeFastLoop;
SeeFastLoop:
// keep sprinting
XZW1 U 0 A_Footstep(1,true);
XZW1 UVWXYZ 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd");
XZW2 A 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd");
XZW2 B 0 A_Footstep(-1,true);
XZW2 BCDEFG 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd");
XZW1 T 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"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();
A_NoBlocking();
}
XZW2 RSTUVWXYZ 2;
XZW3 ABCDEFG 2;
XZW3 H 350;
XZW3 H 1 A_DMFade();
Wait;
Jump:
// start boost
#### # 2;
XZW3 IJKLMNO 2
{
if ( player.onground||bNoGravity||(waterlevel>=3) )
return ResolveState("JumpEnd");
A_BoostUp(true);
return ResolveState(null);
}
// keep boost
XZW3 P 1
{
if ( player.onground||bNoGravity||(waterlevel>=3) )
return ResolveState("JumpEnd");
A_BoostUp(false);
return ResolveState(null);
}
XZW3 P 1 A_JumpIf((vel.z<-10)&&(pos.z>(floorz+80)),"Fall");
Goto Jump+8;
JumpEnd:
// stop boost
#### # 2;
XZW3 PQRSTUVW 2;
Goto Spawn+1;
Fall:
// start fall
#### # 4;
XZW3 XYZ 2 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd");
XZW4 AB 2 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd");
Goto FallLoop;
FallLoop:
// falling
XZW4 CDEFGH 3 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd");
Goto FallLoop;
FallEnd:
// landing
XZW4 CIJKLMN 2;
Goto Spawn+1;
Float:
// start
#### # 2;
XZW4 O 3;
Goto FloatLoop;
FloatLoop:
XZW4 PQRS 6;
Loop;
FloatEnd:
#### # 2;
XZW4 TUVWX 3;
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;
Taunt:
#### # 3;
XZW4 YZ 3;
XZW5 ABCDEFGHIJKLM 3;
Goto Spawn+1;
Approve:
#### # 3;
XZW5 NOPQRSTUVWXYZ 3;
XZW6 ABCD 3;
Goto Spawn+1;
Victory:
#### # 3;
XZW6 EFGHIJKLMNOPQRSTUVW 3;
Goto Spawn+1;
Missile:
Melee:
// attacking
XZW1 A 2;
XZW6 XYZ 2;
XZW7 ABC 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:
XZW7 MN 2;
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZW7 OPQRS 2;
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZW7 TUV 2;
Loop;
CrouchMissile:
XZW7 M 2;
XZW7 WXYZ 2;
XZW8 AB 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();
A_NoBlocking();
}
XZW8 GHIJK 2;
XZW8 L 350;
XZW8 L 1 A_DMFade();
Wait;
EndCrouch:
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
XZW8 MNOPQRS 2;
Goto Spawn+1;
}
}
Class DashTrail : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
Scale 0.3;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetState(FindState("Spawn")+Random[ExploS](0,7));
let t = Spawn("DashTrail2",level.Vec3Offset(pos,vel*.3));
t.target = target;
t.vel = vel*1.2;
let s = Spawn("SWWMSmoke",level.Vec3Offset(pos,vel*1.6));
s.vel = vel*.8;
s.SetShade(Color(1,1,1)*Random[ExploS](64,128));
s.special1 = Random[ExploS](2,4);
s.scale *= 1.4;
s.alpha *= .3;
}
override void Tick()
{
Super.Tick();
// hack
if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION);
}
States
{
Spawn:
JFLB ABCDEFGH 1 Bright
{
A_FadeOut(.2);
A_SetScale(scale.x*.95);
}
Loop;
}
}
Class DashTrail2 : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
Scale 0.2;
Alpha 0.4;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetState(FindState("Spawn")+Random[ExploS](0,7));
}
override void Tick()
{
Super.Tick();
// hack
if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION);
}
States
{
Spawn:
JFLR ABCDEFGH 1 Bright
{
A_FadeOut(.02);
A_SetScale(scale.x*1.04);
if ( waterlevel > 0 )
{
let b = Spawn("SWWMBubble",pos);
b.vel = vel;
b.scale *= scale.x;
Destroy();
}
}
Loop;
}
}
Class DemolitionistRadiusShockwaveTail : Actor
{
Default
{
RenderStyle "Translucent";
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+WALLSPRITE;
+NOTELEPORT;
}
States
{
Spawn:
SDST A 1
{
A_FadeOut(0.08);
A_SetScale(scale.x,scale.y*0.98);
}
Wait;
}
}
Class DemolitionistRadiusShockwave : Actor
{
Default
{
RenderStyle "Translucent";
Speed 10;
DamageFunction int(20*alpha);
DamageType "GroundPound";
Radius 16;
Height 8;
Alpha 0.8;
Scale 1.2;
+NOBLOCKMAP;
+DONTSPLASH;
+MISSILE;
+STEPMISSILE;
+NOEXPLODEFLOOR;
+WALLSPRITE;
+NOTELEPORT;
+RIPPER;
}
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
{
if ( damage <= 0 ) return damage;
if ( (target.mass < LARGE_MASS) && !target.bDONTTHRUST )
{
target.vel.xy += vel.xy.unit()*(5000./max(50,target.mass))*alpha;
target.vel.z += (800./max(50,target.mass))*alpha;
}
return damage;
}
States
{
Spawn:
SDST A 1
{
SetZ(floorz);
Spawn("InvisibleSplasher",Vec3Offset(0,0,2));
let s = Spawn("DemolitionistRadiusShockwaveTail",pos);
s.vel = vel*.8;
s.scale = scale;
s.alpha = alpha;
s.angle = angle;
A_FadeOut(.04);
A_SetScale(scale.x*1.03,scale.y*0.99);
}
Wait;
Death:
SDST A 1
{
SetZ(floorz);
A_FadeOut(.1);
A_SetScale(scale.x*1.06,scale.y*0.98);
}
Wait;
}
}
Class DemolitionistShockwave : Actor
{
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(7,7,7,30,0,300+min(special1,50)*4,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:1.5);
if ( target.player != players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.3);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.2,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.1,pitch:.4);
}
target.A_Blast(BF_AFFECTBOSSES,200+min(special1*3,100),70+min(special1*2,130),10+min(special1/10,20),"SWWMNothing","");
A_Explode(10+min(special1,90),70+min(special1*2,130),XF_EXPLICITDAMAGETYPE,damagetype:'GroundPound');
for ( int i=0; i<360; i+=5 )
{
let r = Spawn("DemolitionistRadiusShockwave",Vec3Angle(5,i));
r.target = target;
r.angle = i;
r.vel.xy = (cos(i),sin(i))*r.speed;
r.alpha *= .1+min(special1*.03,.9);
}
for ( int i=0; i<360; i+=5 )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
let s = Spawn("SWWMSmoke",Vec3Angle(4,i,8));
s.vel = pvel+(cos(i),sin(i),0)*7.;
s.SetShade(Color(1,1,1)*Random[ExploS](64,224));
s.special1 = Random[ExploS](1,4);
s.scale *= 1.5;
s.alpha *= .4;
}
int numpt = Random[ExploS](10,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
}
States
{
Spawn:
TNT1 A 140;
Stop;
}
}
Class ReviveCooldown : Powerup
{
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Revive.png";
Powerup.Duration -30;
}
override void Tick()
{
if ( !Owner ) Destroy();
if ( Owner.Health <= 0 ) return; // timer does not go down when dead
if ( (EffectTics == 0) || ((EffectTics > 0) && (--EffectTics == 0)) )
Destroy ();
}
override void InitEffect()
{
Super.InitEffect();
// adjust the duration
EffectTics = max(0,swwm_revivecooldown)*Thinker.TICRATE;
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("demolitionist/revive",CHAN_ITEMEXTRA);
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_REFAIL"));
}
override void OwnerDied()
{
// do nothing, this "powerup" is preserved on death
}
}
// not an actual light, just handles the attach/detach
Class DemolitionistSelfLight : Actor
{
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
}
bool activelight()
{
// active all the time except when invisible or in certain
// animation frames
if ( target.bINVISIBLE || (target.alpha <= double.epsilon) ) return false;
if ( target.InStateSequence(target.CurState,target.FindState("Death")) && ((target.frame == 20) || (target.frame == 22) || (target.frame == 23) || (target.frame == 25) || (target.frame == 27) || (target.frame == 28) || (target.frame == 30) || (target.frame == 31) || (target.frame == 32) || (target.frame == 33) || (target.frame == 34)) )
return false;
if ( target.InStateSequence(target.CurState,target.FindState("CrouchDeath")) && ((target.frame == 7) || (target.frame == 10) || (target.frame == 11)) )
return false;
return true;
}
override void Tick()
{
Super.Tick();
if ( !target || (Demolitionist(target).selflight != self) )
{
Destroy();
return;
}
if ( !activelight() )
target.A_RemoveLight('DemoSelfLight');
else if ( activelight() )
target.A_AttachLight('DemoSelfLight',DynamicLight.PointLight,Color(56,72,88),200,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE|DynamicLight.LF_SPOT,(12,0,target.player.viewz-target.pos.z),0,30,90,target.pitch);
if ( target.bINVISIBLE || (target.alpha <= double.epsilon) )
target.A_RemoveLight('DemoSelfLight2');
else
target.A_AttachLight('DemoSelfLight2',DynamicLight.PointLight,Color(32,48,24),80,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE,(0,0,target.height/2));
}
}
// for the doom 2 cast
Class CastDemolitionist : Actor
{
Default
{
DeathSound "demolitionist/death";
}
States
{
Spawn:
See:
CCD1 ABCDEFGHIJKLMNOP 2;
Loop;
Missile:
CCD1 A 2;
CCD2 ABCDEF 2;
Goto See;
Death:
CCD1 A 2;
CCD3 ABCDEFGHIJKLMNOP 2;
CCD3 Q -1;
Stop;
}
}