swwmgz_m/zscript/swwm_player.zsc

3421 lines
112 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 fullfuel;
bool sendtoground;
bool key_reentrant;
bool bInDefaultInventory;
bool oldsinglefirst;
int lastdamage;
transient int lastdamagetic;
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
Array<double> bumpvelz, bumppitch;
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;
Actor selflight;
Actor oldencroached;
Vector3 oldencroachedpos;
int encroachtics;
Vector3 pretelepos;
SWWMItemSense itemsense;
int itemsense_cnt;
int healcooldown, healtimer, oldhealth;
bool scriptedinvul;
bool hitactivate;
Actor froggy;
transient int lastuse, failcounter, failcooldown;
transient SWWMItemTracer itrace;
bool meleeuse;
transient bool bWalking;
Property DashFuel : dashfuel;
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;
+DONTMORPH;
+DONTDRAIN;
+DONTCORPSE;
}
// oof
Vector2 NormalizedMove()
{
if ( !(player.cmd.forwardmove|player.cmd.sidemove) )
return (0,0);
Vector2 mvec = (player.cmd.forwardmove,-player.cmd.sidemove/.96);
double maxval = max(abs(mvec.x),abs(mvec.y));
// sorry, we don't use that here specifically, that's for tweakspeed to handle
if ( !(player.cmd.buttons&BT_RUN) ) maxval *= 2.;
return mvec.unit()*maxval;
}
double TweakSpeed()
{
double fact = bWalking?.08:(player.cmd.buttons&BT_RUN)?1.25:.5;
for ( Inventory i=Inv; i; i=i.Inv ) fact *= i.GetSpeedFactor();
return fact;
}
// 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 = 200;
}
if ( giveall || (name ~== "backpack") )
{
let def = GetDefaultByType('HammerspaceEmbiggener');
GiveInventory('TradedHammerspaceEmbiggener',def.MaxAmount,true);
if ( !giveall ) return;
}
if ( giveall || (name ~== "ammo") )
{
// first we must build an array of all valid weapons, this saves time instead of doing recursive loops
Array<Class<Weapon> > validweapons;
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type2 = (class<Weapon>)(AllActorClasses[i]);
if ( !type2 ) continue;
let rep = GetReplacement(type2);
if ( (rep != type2) && !(rep is "DehackedPickup") ) continue;
readonly<Weapon> weap = GetDefaultByType(type2);
if ( !player || !player.weapons.LocateWeapon(type2) || weap.bCheatNotWeapon || !weap.CanPickup(self) ) continue;
let ready = weap.FindState("Ready");
if ( !ready || !ready.ValidateSpriteFrame() ) continue;
validweapons.Push(type2);
}
// 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 == "Ammo") || (type == "SWWMAmmo") || ((type.GetParentClass() != "Ammo") && (type.GetParentClass() != "SWWMAmmo")) )
continue;
// Only give if it's for a valid weapon
bool isvalid = false;
for ( int j=0; j<validweapons.Size(); j++ )
{
readonly<Weapon> weap = GetDefaultByType(validweapons[j]);
if ( (validweapons[j] 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;
}
// also give mag ammo
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<MagAmmo>)(AllActorClasses[i]);
if ( !type || (type.GetParentClass() != "MagAmmo") )
continue;
let pamo = GetDefaultByType(type).ParentAmmo;
// Only give if it's for a valid weapon
bool isvalid = false;
for ( int j=0; j<validweapons.Size(); j++ )
{
readonly<Weapon> weap = GetDefaultByType(validweapons[j]);
if ( (validweapons[j] is 'SWWMWeapon') && SWWMWeapon(weap).UsesAmmo(pamo) )
{
isvalid = true;
break;
}
if ( (weap.AmmoType1 == pamo) || (weap.AmmoType2 == pamo) )
{
isvalid = true;
break;
}
}
if ( !isvalid ) continue;
let magitem = FindInventory(type);
if ( !magitem )
{
magitem = Inventory(Spawn(type));
magitem.AttachToOwner(self);
magitem.Amount = magitem.MaxAmount;
}
else if ( magitem.Amount < magitem.MaxAmount )
magitem.Amount = magitem.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 owned = FindInventory(type);
if ( owned )
{
owned.Amount = owned.MaxAmount;
continue;
}
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 owned = FindInventory(type);
if ( owned )
{
owned.Amount = owned.MaxAmount;
continue;
}
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++ )
{
let type = (Class<Key>)(AllActorClasses[i]);
if ( !type ) continue;
let keyitem = GetDefaultByType(type);
if ( keyitem.special1 )
{
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;
}
}
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 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;
if ( !giveall ) return;
}
if ( giveall || (name ~== "artifacts") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Inventory>)(AllActorClasses[i]);
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);
if ( def.Icon.isValid() && !(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") && !(type is "MagAmmo") && !(type is "Armor") && !(type is "Key") && !(type is "DivineSpriteEffect") )
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;
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
if ( !giveall ) return;
}
if ( (giveall == ALL_YESYES) || (name ~== "collectibles") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<SWWMCollectible>)(AllActorClasses[i]);
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(AllActorClasses[i]));
SWWMCollectible(item).propagated = true; // no score or anims
if ( !item.CallTryPickup(self) ) item.Destroy();
}
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 ( 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 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'));
sc.AttachToOwner(self);
let ap = Inventory(Spawn('AlmasteelPlating'));
ap.AttachToOwner(self);
let dp = Inventory(Spawn('DeepImpact'));
dp.AttachToOwner(self);
let eg = ExplodiumGun(Spawn('ExplodiumGun'));
eg.AttachToOwner(self);
eg.firstselect = true;
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'));
cs.AttachToOwner(self);
}
// 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 is 'HammerspaceEmbiggener') )
{
i.DepleteOrDestroy();
if ( !i.bDestroyed ) last = i;
}
else last = i;
}
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
oldsinglefirst = swwm_singlefirst; // super already sets up the slots, so save the cvar value now
mystats = SWWMStats.Find(player);
lastground = true;
}
void A_Dash()
{
vel += dashdir*dashboost*clamp(dashfuel/20.,0.,1.);
player.vel *= 0.;
if ( dashboost < .2 ) dashboost = 0.;
else
{
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:800);
if ( !(player.cmd.buttons&BT_USER2) ) dashboost *= .1;
}
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
SWWMUtility.AchievementProgressIncDouble('swwm_progress_fuel',fueluse,player);
mystats.fuelusage += fueluse;
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
dashcooldown = min(40,max(10,int(dashcooldown*1.4)));
fuelcooldown = max(30,fuelcooldown);
if ( (dashfuel <= 0.) && fullfuel )
SWWMUtility.AchievementProgressInc('swwm_progress_brake',1,player);
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
SetStateLabel("DashEnd");
}
void A_BoostUp( bool initial = false )
{
vel += (0,0,1)*dashboost*clamp(dashfuel/10.,0,1.);
player.vel *= 0.;
if ( dashboost < .2 ) dashboost = 0.;
else
{
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:800);
dashboost *= (player.cmd.buttons&BT_JUMP)?.95:.4;
last_boost = level.maptime+1;
}
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
SWWMUtility.AchievementProgressIncDouble('swwm_progress_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 SenseItems()
{
let thesight = FindInventory("Omnisight");
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 ( !thesight && !CheckSight(i,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
SWWMItemSense.Spawn(self,i);
}
}
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('swwm_progress_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;
for ( int i=0; i<s.Lines.Size(); i++ )
{
let l = s.Lines[i];
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;
for ( int j=0; j<con.Size(); j++ )
{
if ( (l.v1 != con[j].v1) && (l.v2 != con[j].v2) && (l.v1 != con[j].v2) && (l.v2 != con[j].v1) )
continue;
notmatched = false;
break;
}
if ( notmatched ) continue;
con.Push(l);
found++;
}
}
while ( found > 0 );
for ( int i=0; i<con.Size(); i++ )
con[i].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;
for ( int i=0; i<con.Size(); i++ )
{
Sector s = con[i];
for ( int j=0; j<s.Lines.Size(); j++ )
{
Line l = s.Lines[j];
// 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 );
for ( int i=0; i<con.Size(); i++ )
con[i].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;
for ( int i=0; i<con.Size(); i++ )
{
Sector s = con[i];
for ( int j=0; j<s.Lines.Size(); j++ )
{
Line l = s.Lines[j];
// 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 );
for ( int i=0; i<con.Size(); i++ )
con[i].SetTexture(0,replacewith);
}
}
}
void CheckItemUsePickup()
{
if ( player.usedown )
return;
if ( !itrace ) itrace = new("SWWMItemTracer");
Vector3 x, y, z, dir;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = Vec2OffsetZ(0,0,player.viewz);
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 = (x+y*cos(j)*i+z*sin(j)*i).unit();
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;
}
}
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_UNDERWATER) ) // 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;
}
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 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 = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-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 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 ( !swwm_fly6dof || !((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;
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) )
{
failcooldown = SWWMHandler.AddOneliner("puzzfail",2,20);
failcounter = max(4,failcounter-10);
}
}
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);
}
}
void NearbyItemSparkles()
{
if ( (player != players[consoleplayer]) || !swwm_itemsparkles ) return;
let bt = BlockThingsIterator.Create(self,500);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !(t is 'Inventory') || !t.bSPECIAL || t.bINVISIBLE || Inventory(t).Owner || !SWWMUtility.SphereIntersect(t,pos,500) )
continue;
Vector3 bpos = (0,0,16);
if ( t.bFLOATBOB ) bpos.z += t.GetBobOffset();
double alph = clamp((500.-Distance3D(t))/500.,0.,1.);
Color pcol = "Gold";
if ( Inventory(t).PickupFlash is 'SWWMPickupFlash' )
{
let def = GetDefaultByType(Inventory(t).PickupFlash);
pcol = Color(def.Args[1]*85,def.Args[2]*85,def.Args[3]*85);
}
int numpt = clamp(int(max(t.radius,t.height-16)/4),1,8);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ClientSparkles](0,360);
double pt = FRandom[ClientSparkles](0,360);
double dst = FRandom[ClientSparkles](.5,1.25)*max(t.radius,t.height-16);
Vector3 dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt));
Vector3 ppos = bpos+dir*dst;
t.A_SpawnParticle(pcol,SPF_FULLBRIGHT,30,2.,0,ppos.x,ppos.y,ppos.z,dir.x*.2,dir.y*.2,dir.z*.2,0,0,.05,alph,-1,-2./30.);
}
}
}
override void CheckPoison()
{
// HAHA no
player.poisoncount = 0;
}
override void Tick()
{
Vector3 oldpos = pos;
// can't be poisoned
PoisonDurationReceived = 0;
PoisonPeriodReceived = 0;
PoisonDamageReceived = 0;
Super.Tick();
if ( hasteleported )
{
// we just got teleported, don't count the travel distance
oldpos = pos;
hasteleported = false;
}
if ( !selflight )
{
selflight = Spawn("DemolitionistSelfLight",pos);
selflight.target = self;
selflight.Tick();
}
if ( !player || (player.mo != self) ) return;
NearbyItemSparkles();
// double-check that we have these
if ( !FindInventory("AlmasteelPlating") )
{
let ap = Inventory(Spawn("AlmasteelPlating"));
ap.AttachToOwner(self);
}
if ( !FindInventory("SayaCollar") )
{
let sc = Inventory(Spawn("SayaCollar"));
sc.AttachToOwner(self);
}
// 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 ( waterlevel < 2 )
{
if ( !player.onground || bNoGravity )
{
cairtime++;
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
mystats.airdist += traveldist;
}
else
{
cairtime = 0;
mystats.grounddist += traveldist;
SWWMUtility.AchievementProgressIncDouble('swwm_progress_travel',traveldist/32000.,player);
}
}
else mystats.swimdist += traveldist;
CheckUnderwaterAmb();
if ( player.cmd.buttons&BT_USER3 ) SenseItems();
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
if ( vel.length() > ((3600*GameTicRate)/32000.) )
SWWMUtility.AchievementProgress('swwm_progress_sanic',int((vel.length()*3600*GameTicRate)/32000.),player);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) )
{
// bump down weapon
bumpvelz.Push(-lastvelz);
double bpitch = 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);
bpitch = min(bpitch+30,60);
lastbump *= 1.3;
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++;
}
double newp = min(90,pitch+bpitch);
bumppitch.Push(newp-pitch);
A_SetPitch(newp,SPF_INTERPOLATE);
if ( lastvelz < -10 )
{
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:200);
}
if ( (lastvelz < -gruntspeed) && (swwm_mutevoice < 4) && (health > 0) )
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,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) && !IsFriend(encroached) )
SWWMUtility.AchievementProgress('swwm_progress_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 ( encroached && !encroached.bNOBLOOD && !encroached.bINVULNERABLE )
{
encroached.TraceBleed(realdmg,self);
encroached.SpawnBlood(pos,angle,realdmg);
}
}
}
for ( int i=0; i<bumpvelz.Size(); i++ )
{
lagvel.z += bumpvelz[i]*.2;
bumpvelz[i] *= .8;
if ( abs(bumpvelz[i]) > double.epsilon ) continue;
bumpvelz.Delete(i--);
}
for ( int i=0; i<bumppitch.Size(); i++ )
{
A_SetPitch(pitch-bumppitch[i]/5.,SPF_INTERPOLATE);
bumppitch[i] *= .8;
if ( abs(bumppitch[i]) > double.epsilon ) continue;
bumppitch.Delete(i--);
}
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 = (((waterlevel<2)&&(bFly&&!bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||isdashing);
fuelcooldown = max(0,fuelcooldown-1);
dashcooldown = max(0,dashcooldown-1);
boostcooldown = max(0,boostcooldown-1);
if ( fuelcooldown <= 0 )
{
double oldfuel = dashfuel;
dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.));
// stops
if ( (oldfuel < (default.dashfuel/24)) && (dashfuel >= default.dashfuel/24) )
fuelcooldown = 20;
else if ( (oldfuel < (default.dashfuel/12)) && (dashfuel >= default.dashfuel/12) )
fuelcooldown = 10;
}
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.target = 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 = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(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 ) 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);
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('swwm_progress_bonk',1,player);
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
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 ( 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,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,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
}
}
if ( !busted || !raging )
{
// headbump
bumped = true;
SWWMUtility.AchievementProgressInc('swwm_progress_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);
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
A_SprayDecal("ButtMark",172,(cos(angle+90)*2,sin(angle+90)*2,Height*.54),dir);
A_SprayDecal("ButtMark",172,(cos(angle-90)*2,sin(angle-90)*2,Height*.54),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,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
if ( raging ) continue; // don't stop
}
}
// wallbump
bumped = true;
SWWMUtility.AchievementProgressInc('swwm_progress_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);
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.target = self;
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
}
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('swwm_achievement_crush',player);
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// we still have to ENSURE ENTIRELY that this gets nullified (TELEFRAG_DAMAGE overrides damage factors somehow)
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' )
{
// check if we can break any active crushers
if ( !inflictor && !source ) CheckBreakCrusher();
// 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)+(FRandom[ExploS](-.6,.6),FRandom[ExploS](-.6,.6),FRandom[ExploS](-.6,.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)+(FRandom[ExploS](-1.,1.),FRandom[ExploS](-1.,1.),FRandom[ExploS](-1.,1.))).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('swwm_achievement_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);
// hotfix damagecount overflow until proper fix is in stable gzdoom
if ( player && (player.damagecount < 0) ) player.damagecount = 100;
lastdamage = max(lastdamage,realdmg);
lastdamagetic = max(lastdamagetic,gametic+clamp(lastdamage/2,10,40));
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
{
lastmpain = level.maptime;
A_DemoPain();
}
PainChance = oldpchance;
if ( (Health <= 0) && (source == self) && (flags&DMG_EXPLOSION) )
SWWMUtility.MarkAchievement('swwm_achievement_dime',player);
return realdmg;
}
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 CheckMoveUpDown()
{
if ( InStateSequence(CurState,FindState("Dash")) )
player.cmd.upmove = 0;
if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
double fs = TweakSpeed();
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/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();
}
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 ( (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 ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
if ( player.centering || !swwm_fly6dof ) guideroll = clamp(deltaangle(roll,0),-3.,3.);
if ( swwm_fly6dof )
{
swwm_GM_Quaternion orient = swwm_GM_Quaternion.createFromAngles(angle,pitch,roll);
swwm_GM_Quaternion angles = swwm_GM_Quaternion.createFromAngles(guideangle,guidepitch,guideroll);
orient = orient.multiplyQuat(angles);
double npitch, nangle, nroll;
[nangle, npitch, nroll] = orient.toAngles();
A_SetAngle(nangle,SPF_INTERPOLATE);
A_SetPitch(npitch,SPF_INTERPOLATE);
A_SetRoll(nroll,SPF_INTERPOLATE);
}
else
{
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] = swwm_CoordUtil.GetAxes(pitch,angle,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 *= .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
{
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 && !waterlevel )
{
// 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 ( (waterlevel || bNOGRAVITY) && !player.GetClassicFlight() )
{
double zpush = nmove.x*sin(pitch);
if ( waterlevel && (waterlevel < 2) && (zpush < 0) ) zpush = 0;
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);
player.vel += RotateVector(nmove,angle)*bobfactor*16.;
}
// override air control because we REALLY need the extra mobility
if ( !player.onground && !bNOGRAVITY && !waterlevel )
{
nmove = NormalizedMove();
double fs = TweakSpeed();
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 accel = (RotateVector(nmove,angle),0);
accel *= fs/480.;
double spd = vel.length();
double maxspd = fs*15.;
if ( spd > maxspd ) vel = (vel+accel/GameTicRate).unit()*spd;
else vel = vel+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 ) vel *= .95; // quickly decelerate if we're not holding movement keys
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 && (waterlevel < 2) && (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] = 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.cmd.buttons&BT_CROUCH ) dodge = (0,0,-1); // death from above
if ( dodge == (0,0,0) )
{
if ( !player.onground || bNOGRAVITY || (waterlevel > 2) || (player.cheats&CF_NOCLIP2) ) dodge = X;
else dodge.xy = RotateVector((1,0),angle);
}
if ( player.onground && !bNOGRAVITY && (waterlevel < 2) && !(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++;
double newp = min(90.,pitch+5.);
bumppitch.Push(newp-pitch);
A_SetPitch(newp,SPF_INTERPOLATE);
}
}
override void CheckJump()
{
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
if ( !(player.cmd.buttons&BT_JUMP) || (gamestate != GS_LEVEL) ) return;
Vector2 walldir = (cos(angle),sin(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+(cos(ang+180),sin(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;
walljump = true;
walldir = -(walldir*.7+(cos(ang),sin(ang))*.3);
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;
walljump = true;
walldir = -(walldir*.7+(cos(ang),sin(ang))*.3);
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;
walljump = true;
jumpactor = d.HitActor;
walldir = -(walldir*.7+(cos(ang),sin(ang))*.3);
break;
}
climbvelz = 1.5*sqrt(max(1.,(d.HitActor.pos.z+d.HitActor.Height)-pos.z));
}
jumpactor = d.HitActor;
walldir = (walldir*.7+(cos(ang),sin(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 ( waterlevel >= 2 ) vel.z = 4*Speed;
else if ( (waterlevel < 2) && 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 )
{
A_StartSound(walljump?"demolitionist/kick":"demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:100);
last_kick = level.maptime+1;
SWWMUtility.AchievementProgressInc('swwm_progress_jump',1,player);
}
}
bOnMobj = false;
player.jumpTics = -1;
if ( !(player.cheats&CF_PREDICTING) )
{
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:100);
}
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;
double newp = min(90.,pitch+3.);
bumppitch.Push(newp-pitch);
A_SetPitch(newp,SPF_INTERPOLATE);
SetStateLabel("Boost");
}
else
{
dashboost = 0.;
double bpitch = clamp((vel.length()-10)/5.,0.,20.);
double newp = min(90.,pitch+bpitch);
bumppitch.Push(newp-pitch);
A_SetPitch(newp,SPF_INTERPOLATE);
// bunnyhop time
if ( !walljump && !wallclimb )
{
if ( !bWalking && (level.maptime < (lastairtic+10)) )
{
SWWMUtility.AchievementProgressInc('swwm_progress_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();
}
}
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 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) && swwm_revive )
{
// reboot (if possible)
if ( !FindInventory("ReviveCooldown") && (((swwm_revivecooldown >= 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4)) || !hasrevived) )
{
if ( hasrevived ) SWWMUtility.MarkAchievement('swwm_achievement_sekiro',player);
hasrevived = true;
player.Resurrect();
player.damagecount = 0;
player.bonuscount = 0;
player.poisoncount = 0;
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);
SWWMScoreObj.Spawn(default.health,Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Height/2),ST_Health);
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;
}
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"))
|| 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 && (waterlevel < 1) )
{
// 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 && (waterlevel < 3) )
{
// 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 && (waterlevel < 1) )
{
// 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 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);
if ( lastdamage > 90 )
{
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:1200);
A_QuakeEx(3,3,3,9,0,8,"",QF_RELATIVE|QF_SCALEDOWN);
A_StartSound("demolitionist/hipain",CHAN_VOICE);
lastbump *= 1.04;
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
if ( swwm_mutevoice < 4 )
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
else if ( lastdamage > 30 )
{
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:600);
A_QuakeEx(2,2,2,6,0,8,"",QF_RELATIVE|QF_SCALEDOWN);
A_StartSound("demolitionist/pain",CHAN_VOICE);
lastbump *= 1.02;
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
if ( swwm_mutevoice < 4 )
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
else if ( lastdamage > 0 )
{
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:200);
A_QuakeEx(1,1,1,3,0,8,"",QF_RELATIVE|QF_SCALEDOWN);
A_StartSound("demolitionist/lopain",CHAN_VOICE);
lastbump *= 1.01;
if ( !player || (player.mo != self) || (lastdamage < 5) ) return; // voodoo dolls have no voice
if ( swwm_mutevoice < 4 )
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
lastdamage = 0;
}
void A_DemoScream()
{
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
A_StopSound(CHAN_DEMOVOICE);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
Sound snd = "demolitionist/death";
if ( special1 < 10 )
snd = "demolitionist/wdeath";
if ( health < -50 )
snd = "demolitionist/xdeath";
A_StartSound(snd,CHAN_VOICE);
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
if ( swwm_mutevoice < 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 = 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;
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),player.GetUserName(),score);
SWWMUtility.AchievementProgressInc('swwm_progress_allsecrets',1,player);
}
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score);
if ( CheckLocalView() ) SWWMHandler.AddOneliner("findsecret",2,40);
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 void AddInventory( Inventory item )
{
// hackaround for replaced keys
if ( (item is 'Key') && !key_reentrant )
{
let rep = (Class<Inventory>)(GetReplacement(item.GetClass()));
if ( rep && (item.GetClass() != rep) )
{
// also add the new key
Actor whom = player?player.mo:PlayerPawn(self);
if ( !whom.FindInventory(rep) )
{
let nk = Inventory(Spawn(rep));
nk.AttachToOwner(whom);
nk.Use(true);
}
Super.AddInventory(item);
return;
}
}
Super.AddInventory(item);
if ( !player ) return;
if ( !bInDefaultInventory )
{
// add lore if any
SWWMLoreLibrary.Add(player,item.GetClassName());
// weapon get oneliner
if ( (item is 'Weapon') && !(item is 'SWWMGesture') && !(item is 'SWWMItemGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() )
SWWMHandler.AddOneliner("getweapon",2);
}
if ( (item is 'Key') && !key_reentrant && !deathmatch && !bInDefaultInventory )
{
// score
int score = 100;
Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(100,player.mo.Vec3Offset(0,0,Height/2));
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;
let tkey = Inventory(Spawn(item.GetClass()));
SWWMHandler.KeyTagFix(tkey);
if ( tkey is 'SWWMKey' ) SWWMKey(tkey).propagated = true; // no anim
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') && mystats )
{
let cls = item.GetClass();
if ( (mystats.ownedcollectibles.Size() > 0) && (mystats.ownedcollectibles.Find(cls) < mystats.ownedcollectibles.Size()) ) return;
mystats.ownedcollectibles.Push(cls);
}
}
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, 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.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/3600.)*TweakSpeed();
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:(200*vol));
}
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.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/4800.)*TweakSpeed();
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:(100*vol));
}
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.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/6400.)*TweakSpeed();
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:(50*vol));
}
}
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;
}
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);
}
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) )
{
if ( waterlevel >= 2 ) vel.z = -4*Speed;
else if ( 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;
}
override void Travelled()
{
// reinitialize
dashfuel = default.dashfuel;
last_boost = 0;
last_kick = 0;
hasrevived = false;
// 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.Push(self);
// re-attach shadow
if ( swwm_shadows <= 0 ) return;
let ti = ThinkerIterator.Create("SWWMShadow");
Actor a;
while ( a = Actor(ti.Next()) )
{
if ( a.target == self )
return; // shadow already attached
}
SWWMShadow.Track(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");
}
States
{
Spawn:
// normal idle
#### # 2;
XZW1 A 1
{
if ( !player || (player.mo != self) ) return ResolveState("VoodooSpawn");
return 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
{
if ( !player || (player.mo != self) ) return ResolveState("VoodooPain");
return 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
{
if ( !player || (player.mo != self) ) return ResolveState("VoodooDeath");
return 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||(waterlevel>=3) )
return ResolveState("BoostEnd");
A_BoostUp(true);
return ResolveState(null);
}
// keep boost
XZW3 P 1
{
if ( player.onground||bNoGravity||(waterlevel>=3) )
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&&(waterlevel<3),"FallEnd");
Goto FallLoop;
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;
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 NOPQRSTUVWXYZ 3;
XZW6 ABCD 3;
Goto Spawn+1;
Victory:
#### # 3;
XZW6 EFGHIJKLMNOPQRSTUVW 3;
Goto Spawn+1;
BlowKiss:
#### # 3;
XZWD EFGHIJKLMNOPQRSTUVW 3;
Goto Spawn+1;
Headpat:
#### # 3;
XZWH ST 3;
XZWH UVW 2;
XZWH XYZ 1;
XZWI A 1;
XZWI BCDE 2;
XZWI FG 1;
HeadpatLoop:
XZWI H 1;
XZWH XYZ 1;
XZWI A 1;
XZWI BCDE 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;
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 PQRSTUVWXYZ 3;
XZWG ABCDEF 3;
Goto Crouch+1;
CrouchVictory:
#### # 3;
XZWG GHIJKLMNOPQRSTUVWXY 3;
Goto Crouch+1;
CrouchBlowKiss:
#### # 3;
XZWG Z 3;
XZWH ABCDEFGHIJKLMNOPQR 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;
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();
A_NoBlocking();
}
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;
VoodooSpawn:
XZWZ A -1;
Loop;
VoodooPain:
XZWZ A 1;
XZWZ B 1 A_DemoPain();
XZWZ CDEF 1;
Goto VoodooSpawn;
VoodooDeath:
XZWZ A 2
{
A_DemoScream();
A_NoBlocking();
}
XZWZ GHIJKLMNO 2;
XZWZ PQR 2;
XZWZ 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;
}
}