swwmgz_m/zscript/swwm_player.zsc
Marisa the Magician 4a46817881 HUD face now also affected by tag color.
Added two more tag colors to round up to 16.
Fixed tag light color not updating when changed.
Recalculated tag light colors.
2022-10-13 13:43:32 +02:00

4061 lines
130 KiB
Text

// The Demolitionist
Enum EDemoFaceState
{
FS_DEFAULT,
FS_EVIL,
FS_GRIN,
FS_WINK,
FS_BLINK,
FS_SAD,
FS_PAIN,
FS_OUCH,
FS_DEAD // UNUSED
};
Class Demolitionist : PlayerPawn
{
int last_jump_held, last_boost, last_kick;
Vector3 dashdir;
double dashfuel, dashboost;
int dashcooldown, boostcooldown, fuelcooldown;
int dashlockst;
bool fullfuel;
bool sendtoground;
bool key_reentrant;
bool bInDefaultInventory;
bool oldsinglefirst;
transient int lastdamage;
transient int lastdamagetic, lastdamagetimer;
bool lastground;
int lastgroundtic, lastairtic;
double lastvelz, prevvelz, landvelz;
double ssup;
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;
DemolitionistSelfLight selflight;
Actor oldencroached;
Vector3 oldencroachedpos;
int encroachtics;
Vector3 pretelepos;
SWWMItemSense itemsense;
int healcooldown, healtimer, oldhealth;
bool scriptedinvul;
bool hitactivate;
Actor froggy;
transient int lastuse, failcounter, failcooldown;
transient SWWMItemTracer itrace;
bool meleeuse;
transient bool bWalking;
int airscreamtime;
enum EInvWipe
{
WIPE_EPISODE = 1,
WIPE_CLUSTER = 2,
WIPE_MAP = 4
}
int invwipe; // inventory wipe flags for next level
transient int lastbang;
transient bool ingivecheat;
Property DashFuel : dashfuel;
EDemoFaceState facestate;
int paindir;
int facetimer;
int blinktime;
transient int oldfaceidx;
transient int rss;
transient bool facedamage, facegrin, facesad, facewink, faceblink;
transient CVar tagcolor;
int oldtagcolor;
transient int magtime;
SWWMMagItem magitem;
int magitem_cnt;
Default
{
Tag "$T_DEMOLITIONIST";
Speed 1;
Radius 16; // should be 9 in theory, but it'd be too thin
Height 56;
Mass 500;
PainChance 255;
MaxSlopeSteepness 0; // mountain goat mode, all slopes are walkable
Player.DisplayName "$T_DEMOLITIONIST";
// StartItem array is defined but not used directly
// just declared here for mod compat
Player.StartItem "ExplodiumGun";
Player.StartItem "DeepImpact";
Player.StartItem "AlmasteelPlating";
Player.StartItem "SayaCollar";
Player.ViewHeight 52;
Player.AirCapacity 0;
Player.GruntSpeed 20;
Player.ForwardMove 1., 1.;
Player.SideMove 1., 1.;
Player.SoundClass "demolitionist";
DamageFactor "Drowning", 0.;
DamageFactor "Poison", 0.;
DamageFactor "PoisonCloud", 0.;
DamageFactor "Falling", 0.;
Demolitionist.DashFuel 240.;
+NOBLOOD;
+DONTGIB;
+NOICEDEATH;
+NOSKIN;
+DONTMORPH;
+DONTDRAIN;
+DONTCORPSE;
-WINDTHRUST; // too heavy
}
private int GetRandom()
{
return (rss = (rss<<1)*35447+(rss/87));
}
private void UpdateTags()
{
if ( !tagcolor ) tagcolor = CVar.GetCVar('swwm_tagcolor',player);
static const String colname[] =
{
"",
"Blue",
"Cyan",
"Dragonfly",
"Gold",
"Magenta",
"Orange",
"Peach",
"Pink",
"Purple",
"Red",
"Violet",
"White",
"Yellow",
"Black",
"Rust"
};
int idx = tagcolor.GetInt();
if ( (idx < 0) || (idx >= colname.Size()) ) idx = 0;
if ( idx != oldtagcolor )
A_ChangeModel("",0,"","",0,"models","DemoTags"..colname[idx]..".png",CMDL_USESURFACESKIN,-1);
oldtagcolor = idx;
for ( Inventory i=inv; i; i=i.inv )
{
if ( !(i is 'SWWMWeapon') ) continue;
SWWMWeapon(i).UpdateTags(idx,colname[idx]);
}
}
private void UpdateFace()
{
// damage handling
if ( facedamage )
{
if ( lastdamage > 70 )
{
facestate = FS_OUCH;
facetimer = (lastdamagetimer-gametic)+10;
}
else if ( facestate < FS_OUCH )
{
facestate = FS_PAIN;
facetimer = (lastdamagetimer-gametic)+10;
paindir = 0;
// paraphrased from vanilla, with some tweaks
if ( player.attacker && (player.attacker != self) )
{
double atkang = AngleTo(player.attacker);
double angdiff = deltaangle(angle,atkang);
if ( abs(angdiff) < 135 )
{
if ( angdiff > 45 ) paindir = -1;
else if ( angdiff < -45 ) paindir = 1;
}
}
}
}
facedamage = false;
if ( facegrin && (facestate < FS_SAD) )
{
facestate = FS_GRIN;
facetimer = 50;
}
facegrin = false;
if ( facewink && (facestate < FS_SAD) )
{
facestate = FS_WINK;
facetimer = 20;
}
facewink = false;
if ( faceblink && (facestate < FS_PAIN) )
{
facestate = FS_BLINK;
facetimer = 30;
}
faceblink = false;
if ( facesad && (facestate <= FS_SAD) )
{
facestate = FS_SAD;
facetimer = 50;
}
facesad = false;
if ( FindInventory("RagekitPower") && (facestate < FS_PAIN) )
{
facestate = FS_EVIL;
facetimer = 10;
}
if ( facetimer > 0 )
{
facetimer--;
if ( facetimer <= 0 )
{
facestate = FS_DEFAULT;
blinktime = 30;
}
}
if ( !(gametic&1) )
{
if ( blinktime <= 0 )
{
blinktime--;
if ( blinktime < -3 )
{
rss = MSTime();
blinktime = (abs(GetRandom())%10)?(40+abs(GetRandom())%40):6;
}
}
else blinktime--;
}
// update our face texture if different
static const String facetex[] =
{
"Blank", "Blink", "Booty", "Dead",
"Default", "Dizzy", "Evil", "Grin",
"Hurt", "HurtLeft", "HurtRight",
"Off", "Ouch", "Sad", "Smug",
"Unamused", "Wink"
};
int faceidx = GetFaceTex();
if ( !oldfaceidx || (faceidx != oldfaceidx) )
A_ChangeModel("",0,"","",1,"models","DemoFace_"..facetex[faceidx]..".png",CMDL_USESURFACESKIN,-1);
oldfaceidx = faceidx;
}
private int GetFaceTex()
{
if ( player.Health <= 0 ) return 3;
if ( (bInvulnerable || (player.cheats&(CF_GODMODE|CF_GODMODE2)) || FindInventory("InvinciballPower")) && (facestate >= FS_PAIN) ) return 14;
if ( facestate == FS_OUCH ) return 12;
if ( facestate == FS_PAIN ) return (paindir==1)?10:(paindir==-1)?9:8;
if ( facestate == FS_GRIN ) return 7;
if ( facestate == FS_EVIL ) return 6;
if ( facestate == FS_SAD ) return 13;
if ( facestate == FS_WINK ) return 16;
if ( facestate == FS_BLINK ) return ((facetimer>28)||(facetimer<2))?15:1;
switch ( blinktime )
{
case -1:
case -3:
return 15;
break;
case -2:
return 1;
break;
}
return 4;
}
// directional movement without straferunning
Vector2 NormalizedMove()
{
if ( !(player.cmd.forwardmove|player.cmd.sidemove) )
return (0,0);
int idx = !!(player.cmd.buttons&BT_RUN);
// ratio between forwardmove and sidemove (depending on BT_RUN state)
double fs = gameinfo.normforwardmove[idx]/gameinfo.normsidemove[idx];
// raw axes scaled to 1:1 ratio
Vector2 mvec = (player.cmd.forwardmove,-player.cmd.sidemove*fs);
// multiply unit vector back to "raw" running speed (as TweakSpeed handles the "true" scaling for us later)
return mvec.unit()*gameinfo.normforwardmove[1]*256.;
}
double TweakSpeed()
{
double fact = bWalking?.08:(player.cmd.buttons&BT_RUN)?1.25:.5;
for ( Inventory i=Inv; i; i=i.Inv ) fact *= i.GetSpeedFactor();
return fact;
}
// the give cheat, oh boy
void CheatGive_Health( int amount = 0 )
{
player.health = health = (amount>0)?(health+amount):default.health;
}
void CheatGive_Backpack()
{
let def = GetDefaultByType('HammerspaceEmbiggener');
GiveInventory('TradedHammerspaceEmbiggener',def.MaxAmount,true);
}
void CheatGive_Ammo()
{
// Max out all mod ammo
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Ammo>)(AllActorClasses[i]);
if ( !type || type.IsAbstract() || (type.GetParentClass() != 'SWWMAmmo') ) continue;
let ammoitem = Ammo(FindInventory(type));
if ( !ammoitem )
{
// Add it first if not found
ammoitem = Ammo(Spawn(type));
ammoitem.Amount = 0;
ammoitem.AttachToOwner(self);
}
// Don't give spares unless we own a Candy Gun, for consistency
if ( (type is 'CandyGunSpares') && !FindInventory('CandyGun') ) continue;
// Top up
ammoitem.Amount = ammoitem.MaxAmount;
// Does it have mag ammo?
let sammoitem = SWWMAmmo(ammoitem);
if ( !sammoitem || !sammoitem.MagAmmoType ) continue;
let magitem = MagAmmo(FindInventory(sammoitem.MagAmmoType));
if ( !magitem )
{
// Add it first if not found (shouldn't happen)
magitem = MagAmmo(Spawn(sammoitem.MagAmmoType));
magitem.AttachToOwner(self);
}
// Top up
magitem.Amount = magitem.MaxAmount;
}
}
void CheatGive_Armor()
{
// only give armors that have spares associated
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (Class<SWWMSpareArmor>)(AllActorClasses[i]);
if ( !type || type.IsAbstract() || (type == 'SWWMSpareArmor') ) continue;
if ( GetReplacement(type) != type ) continue;
let def = GetDefaultByType(type);
let armo = SWWMArmor(FindInventory(def.giveme));
if ( !armo )
{
armo = SWWMArmor(Spawn(def.giveme));
armo.AttachToOwner(self);
}
armo.Amount = armo.MaxAmount;
}
}
void CheatGive_Keys()
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (Class<Key>)(AllActorClasses[i]);
if ( !type ) continue;
let keyitem = GetDefaultByType(type);
if ( !keyitem.special1 ) continue;
let rep = GetReplacement(type); // handle replaced keys
if ( !(rep is 'Key') ) continue;
// iwad restrictions (vanilla doesn't care, but here they'll show in the inventory)
if ( !(gameinfo.gametype&GAME_HERETIC) && ((rep is 'SWWMKeyGreen') || (rep is 'SWWMKeyBlue') || (rep is 'SWWMKeyYellow')) )
continue;
if ( !(gameinfo.gametype&GAME_DOOM) && ((rep is 'SWWMRedCard') || (rep is 'SWWMBlueCard') || (rep is 'SWWMYellowCard')) )
continue;
let item = Inventory(Spawn(rep));
SWWMHandler.KeyTagFix(item);
if ( item is 'SWWMKey' ) SWWMKey(item).propagated = true; // no anim
key_reentrant = true;
if ( !item.CallTryPickup(self) ) item.Destroy();
key_reentrant = false;
}
}
void CheatGive_Weapons()
{
let savedpending = player.PendingWeapon;
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;
}
void CheatGive_Artifacts( int amount = 0 )
{
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);
// must have INVBAR and a valid icon (also can't be a puzzle item)
if ( !def.bINVBAR || !def.ShouldSpawn() || !def.Icon.isValid() || (type is 'PuzzleItem') ) continue;
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
}
void CheatGive_PuzzlePieces( int amount = 0 )
{
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);
}
}
void CheatGive_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();
}
}
override void CheatGive( String name, int amount )
{
if ( !player.mo || (player.health <= 0) ) return;
ingivecheat = true;
int giveall = (name~=="everything")?ALL_YESYES:(name~=="all")?ALL_YES:ALL_NO;
if ( giveall )
{
CheatGive_Health();
CheatGive_Backpack();
CheatGive_Armor();
CheatGive_Keys();
CheatGive_Weapons();
CheatGive_Ammo();
CheatGive_Artifacts();
CheatGive_PuzzlePieces();
if ( giveall == ALL_YESYES )
CheatGive_Collectibles();
}
else if ( name ~== "health" ) CheatGive_Health(amount);
else if ( name ~== "backpack" ) CheatGive_Backpack();
else if ( name ~== "ammo" ) CheatGive_Ammo();
else if ( name ~== "armor" ) CheatGive_Armor();
else if ( name ~== "keys" ) CheatGive_Keys();
else if ( name ~== "weapons" ) CheatGive_Weapons();
else if ( name ~== "artifacts" ) CheatGive_Artifacts(amount);
else if ( name ~== "puzzlepieces" ) CheatGive_PuzzlePieces(amount);
else if ( name ~== "collectibles" ) CheatGive_Collectibles();
else
{
Class<Inventory> type = name;
if ( !type || type.IsAbstract() )
{
if ( CheckLocalView() )
Console.Printf("'%s' is not a valid inventory item",name);
ingivecheat = false;
return;
}
GiveInventory(type,amount,true);
}
ingivecheat = false;
}
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'));
if ( !sc.CallTryPickup(self) ) sc.Destroy();
let ap = Inventory(Spawn('AlmasteelPlating'));
if ( !ap.CallTryPickup(self) ) ap.Destroy();
let dp = Inventory(Spawn('DeepImpact'));
if ( !dp.CallTryPickup(self) ) dp.Destroy();
let eg = ExplodiumGun(Spawn('ExplodiumGun'));
eg.firstselect = true;
if ( !eg.CallTryPickup(self) ) eg.Destroy();
else player.ReadyWeapon = player.PendingWeapon = eg;
// in hexdd, we start with the chaos sphere in our grasp
if ( SWWMUtility.IsDeathkings() && SWWMUtility.CheckMD5List("vanillahexen.list") )
{
let cs = Inventory(Spawn('SWWMChaosSphere'));
if ( !cs.CallTryPickup(self) ) cs.Destroy();
}
// in deathmatch, we start with 8 embiggeners
if ( deathmatch )
{
let em = Inventory(Spawn('TradedHammerspaceEmbiggener'));
em.Amount = em.MaxAmount;
if ( !em.CallTryPickup(self) ) em.Destroy();
}
bInDefaultInventory = false;
}
override void ClearInventory()
{
Super.ClearInventory();
// remove some specific "undroppable" items
Actor last = self;
while ( last.inv )
{
let i = last.inv;
if ( ((i is 'SWWMArmor') && !i.bUNCLEARABLE) || (i is 'HammerspaceEmbiggener') )
{
i.DepleteOrDestroy();
if ( !i.bDestroyed ) last = i;
}
else last = i;
}
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
lastground = true; // prevent sudden landing sound on map start
blinktime = 30;
// swap ourselves for a voodoo doll
if ( !player || (player.mo != self) )
{
let v = Spawn("SWWMVoodooDoll",pos);
v.angle = angle;
v.player = player;
// give it a face if it belongs to a player
if ( player )
{
v.bFRIENDLY = true;
v.A_ChangeModel("",0,"","",1,"models","VoodooDollFace.png",CMDL_USESURFACESKIN,-1);
}
Destroy();
return;
}
oldsinglefirst = swwm_singlefirst; // super already sets up the slots, so save the cvar value now
mystats = SWWMStats.Find(player);
// sanity checks
if ( !EventHandler.Find("SWWMHandler") || !StaticEventHandler.Find("SWWMStaticHandler") )
ThrowAbortException("Panic! SWWM event handlers not detected!");
}
void A_Dash()
{
vel += dashdir*dashboost*clamp(dashfuel/20.,0.,1.);
player.vel *= 0.;
if ( dashboost < .2 ) dashboost = 0.;
else if ( !(player.cmd.buttons&BT_USER2) ) dashboost *= .1;
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
SWWMUtility.AchievementProgressIncDouble("fuel",fueluse,player);
mystats.fuelusage += fueluse;
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
dashcooldown = min(40,max(10,int(dashcooldown*1.4)));
dashlockst = clamp(dashcooldown/3,2,10);
fuelcooldown = max(30,fuelcooldown);
if ( (dashfuel <= 0.) && fullfuel )
SWWMUtility.AchievementProgressInc("brake",1,player);
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
SetStateLabel("DashEnd");
}
void A_BoostUp( bool initial = false )
{
vel.z += .25*dashboost*clamp(dashfuel/20.,0,1.);
player.vel *= 0.;
if ( dashboost < .2 ) dashboost = 0.;
else
{
if ( player.cmd.buttons&BT_JUMP ) dashboost = min(5.,dashboost*1.01);
else dashboost *= .4;
last_boost = level.maptime+1;
}
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
SWWMUtility.AchievementProgressIncDouble("fuel",fueluse,player);
mystats.fuelusage += fueluse;
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
if ( ((dashfuel <= 0.) || (dashboost <= 0.)) )
{
if ( !initial )
{
if ( player.onground ) SetStateLabel("JumpEnd");
else SetStateLabel("Fall");
}
return;
}
fuelcooldown = max(20,fuelcooldown);
}
void SenseItems()
{
if ( player.cmd.buttons&BT_USER3 )
{
let bt = BlockThingsIterator.Create(self,800);
while ( bt.Next() )
{
let i = bt.Thing;
if ( !i || (!(i is 'Inventory') && !(i is 'Chancebox') && !(i is 'SWWMRespawnTimer')) ) continue;
if ( (i is 'Inventory') && (i.bINVISIBLE || !i.bSPECIAL || Inventory(i).Owner) ) continue;
if ( (i is 'Chancebox') && (i.CurState != i.SpawnState) ) continue;
if ( !SWWMUtility.SphereIntersect(i,pos,800) ) continue;
if ( !level.allmap && !CheckSight(i,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
SWWMItemSense.Spawn(self,i);
}
bt.Destroy();
}
SWWMItemsense itm = itemsense;
SWWMItemsense prev = null, next;
while ( itm )
{
next = itm.next;
if ( itm.Tick() )
{
if ( prev ) prev.next = next;
else itemsense = next;
itm.Destroy();
}
else prev = itm;
itm = next;
}
}
void CheckDefaceTexture()
{
if ( player.usedown )
return;
FLineTraceData d;
LineTrace(angle,DEFMELEERANGE*2,pitch,TRF_THRUACTORS,player.viewheight,data:d);
if ( d.HitType == TRACE_HitNone ) return;
bool remove;
TextureID replacewith;
[remove, replacewith] = SWWMUtility.DefaceTexture(d.HitTexture);
if ( !remove ) return;
if ( (d.HitType != TRACE_HitWall) || !d.HitLine.special || !(d.HitLine.activation&SPAC_Use) )
player.usedown = true;
A_StartSound("bestsound",CHAN_ITEMEXTRA,CHANF_OVERLAP);
lastbump *= .97;
int scr = (TexMan.GetName(d.HitTexture).Left(6)~=="ZZWOLF")?200:20;
if ( scr == 20 ) SWWMUtility.AchievementProgressInc("doodle",1,player);
SWWMCredits.Give(player,scr);
if ( player == players[consoleplayer] ) SWWMScoreObj.Spawn(scr,d.HitLocation);
if ( d.HitType == TRACE_HitWall )
{
if ( d.Hit3DFloor )
{
// TODO connected textures for upper/lower
if ( d.Hit3DFloor.flags&F3DFloor.FF_UPPERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(0,replacewith);
else if ( d.Hit3DFloor.flags&F3DFloor.FF_LOWERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(2,replacewith);
else d.Hit3DFloor.master.sidedef[0].SetTexture(1,replacewith);
}
else
{
// find connected sidedefs with the same texture
Array<Line> con;
con.Clear();
con.Push(d.HitLine);
Sector s = d.LineSide?d.HitLine.backsector:d.HitLine.frontsector;
int found = 0;
do
{
found = 0;
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 = SWWMUtility.ConeSpread(x,y,z,j,i);
itrace.Trace(origin,os,dir,UseRange,0);
if ( itrace.Results.HitType != TRACE_HitActor ) continue;
if ( ignoreme.Find(itrace.Results.HitActor) < ignoreme.Size() ) continue;
player.usedown = true; // we found an item, ignore further uses
if ( itrace.Results.HitActor.Used(self) ) return;
ignoreme.Push(itrace.Results.HitActor);
}
rings += 2;
}
}
void CheckItemMagnet()
{
if ( magtime > 40 )
{
if ( !(player.cmd.buttons&BT_USE) || swwm_usetopickup )
{
magtime = 0;
SWWMMagItem mi = magitem;
while ( mi )
{
let next = mi.next;
mi.Destroy();
mi = next;
}
magitem_cnt = 0;
return;
}
if ( (magitem_cnt < 8) && !swwm_usetopickup )
{
let bt = BlockThingsIterator.Create(self,500);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !(t is 'Inventory') || !t.bSPECIAL || !t.bDROPPED || t.bINVISIBLE || Inventory(t).Owner || !SWWMUtility.SphereIntersect(t,pos,500) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
let i = Inventory(t);
Class<Inventory> cls = i.GetClass();
if ( i is 'Ammo' ) cls = Ammo(i).GetParentAmmo();
else if ( i is 'MaGammo' ) cls = MagAmmo(i).GetParentMagAmmo();
let oi = FindInventory(cls);
if ( !i.bALWAYSPICKUP && oi && (oi.Amount >= oi.MaxAmount) ) continue;
if ( (i is 'SWWMWeapon') && SWWMWeapon(i).HasSwapWeapon(self) && swwm_swapweapons ) continue;
if ( (i is 'SWWMDualWeaponGiver') && SWWMDualWeaponGiver(i).HasSwapWeapon(self) && swwm_swapweapons ) continue;
bool addme = true;
for ( SWWMMagItem mi=magitem; mi; mi=mi.next )
{
if ( mi.item != i ) continue;
addme = false;
break;
}
if ( !addme ) continue;
let nmi = new("SWWMMagItem");
nmi.target = self;
nmi.item = i;
nmi.next = magitem;
i.A_StartSound("misc/magitem",CHAN_AMBEXTRA,CHANF_LOOP,.2,1.,.5);
i.bNOGRAVITY = true;
magitem = nmi;
magitem_cnt++;
}
bt.Destroy();
}
SWWMMagItem itm = magitem;
SWWMMagItem prev = null, next;
while ( itm )
{
next = itm.next;
if ( itm.Tick() )
{
if ( prev ) prev.next = next;
else magitem = next;
itm.Destroy();
magitem_cnt--;
}
else prev = itm;
itm = next;
}
return;
}
if ( player.cmd.buttons&BT_USE ) magtime++;
}
void CheckUnderwaterAmb( bool restore = false )
{
Vector3 headpos = Vec3Offset(0,0,player.viewheight);
Vector3 centerpos = Vec3Offset(0,0,height/2);
Sector headregion = null;
if ( CurSector.moreflags&Sector.SECMF_UNDERWATER ) // check underwater sector
headregion = CurSector;
else if ( CurSector.heightsec && (Cursector.heightsec.moreflags&Sector.SECMF_UNDERWATERMASK) ) // check height transfer
{
let hsec = CurSector.heightsec;
double fh = hsec.floorplane.ZAtPoint(pos.xy);
if ( pos.z < fh )
{
if ( headpos.z <= fh )
headregion = hsec;
}
else if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY) && (headpos.z > hsec.ceilingplane.ZAtPoint(pos.xy)) )
headregion = hsec;
}
else // check 3D floors
{
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
let ff = CurSector.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || !(ff.flags&F3DFloor.FF_SWIMMABLE) ) continue;
double ff_bottom = ff.bottom.ZAtPoint(pos.xy);
double ff_top = ff.top.ZAtPoint(pos.xy);
if ( (ff_top <= pos.z) || (ff_bottom > centerpos.z) ) continue;
if ( headpos.z <= ff_top )
headregion = ff.model;
break;
}
}
int curunder = UNDER_NONE;
if ( headregion )
{
switch ( headregion.damagetype )
{
case 'Fire':
case 'Lava':
curunder = UNDER_LAVA;
break;
case 'Slime':
case 'Poison':
case 'PoisonCloud':
curunder = UNDER_SLIME;
break;
case 'Ice':
case 'Drowning':
default:
curunder = UNDER_WATER;
break;
}
undercol = headregion.ColorMap.LightColor;
}
if ( (curunder != lastunder) || restore )
{
static const string undersnd[] = {"","misc/underwater","misc/underslime","misc/underlava"};
static const string entersnd[] = {"","misc/waterenter","misc/slimeenter","misc/lavaenter"};
static const string exitsnd[] = {"","misc/waterexit","misc/slimeexit","misc/lavaexit"};
A_StopSound(CHAN_AMBEXTRA);
if ( curunder > UNDER_NONE )
{
A_StartSound(undersnd[curunder],CHAN_AMBEXTRA,CHANF_LOOP|CHANF_UI);
if ( !restore && (players[consoleplayer].Camera == self) )
A_StartSound(entersnd[curunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
}
if ( !restore && (lastunder > UNDER_NONE) && (players[consoleplayer].Camera == self) )
A_StartSound(exitsnd[lastunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
}
if ( curunder > UNDER_NONE )
A_SoundVolume(CHAN_AMBEXTRA,(players[consoleplayer].Camera==self)?1.:0.);
lastunder = curunder;
}
override void CheckFOV()
{
if ( !player ) return;
float desired = player.desiredfov;
// adjust fov from weapon (abs due to special use of negative
// to prevent it from scaling look sensitivity)
if ( (player.playerstate != PST_DEAD) && player.readyweapon
&& player.readyweapon.fovscale )
desired *= abs(player.readyweapon.fovscale);
// additional fov bump from various effects
// akin to the old A_ZoomFactor trick, but not limited to weapons and can stack
if ( lastbump <= 0. ) lastbump = 1.;
if ( lastbump != 1. )
{
if ( !bumpstr ) bumpstr = CVar.GetCVar('swwm_bumpstrength',player);
double str = bumpstr.GetFloat();
player.fov *= lastbump*str+1.-str;
lastbump = 1.;
}
// adjust fov from dashing
double spd = vel.length();
if ( InStateSequence(CurState,FindState("Dash")) && (spd > 10.) )
{
Vector3 facedir = SWWMUtility.Vec3FromAngles(angle,pitch);
if ( spd > 0. )
{
double rel = max(0,vel.unit() dot facedir);
desired *= 1.+clamp(rel*(spd-10.),-80.,80.)*.002;
}
}
if ( player.fov == desired ) return;
// interpolate towards desired fov
if ( abs(player.fov-desired) < .1 ) player.fov = desired;
else
{
float zoom = max(.1,abs(player.fov-desired)*.35);
if ( player.fov > desired ) player.fov -= zoom;
else player.fov += zoom;
}
}
override 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 = SWWMUtility.Lerp(oldangle,angle,ticfrac);
double fpitch = SWWMUtility.Lerp(oldpitch,pitch,ticfrac);
double flagangle = SWWMUtility.Lerp(oldlagangle,lagangle,ticfrac);
double flagpitch = SWWMUtility.Lerp(oldlagpitch,lagpitch,ticfrac);
double diffang = fangle-flagangle;
double diffpitch = fpitch-flagpitch;
if ( abs(diffang) > 1. )
{
int sgn = (diffang>0)?1:-1;
diffang = abs(diffang)**.7*sgn;
}
if ( abs(diffpitch) > 1. )
{
int sgn = (diffpitch>0)?1:-1;
diffpitch = abs(diffpitch)**.7*sgn;
}
Vector3 flagvel = SWWMUtility.LerpVector3(oldlagvel,lagvel,ticfrac);
double diffx = flagvel dot SWWMUtility.AngleToVector3(flagangle+90);
double diffy = flagvel dot (0,0,1);
if ( abs(diffx) > 1. )
{
int sgn = (diffx>0)?1:-1;
diffx = abs(diffx)**.5*sgn;
}
if ( abs(diffy) > 1. )
{
int sgn = (diffy>0)?1:-1;
diffy = abs(diffy)**.5*sgn;
}
cur.x += diffang;
cur.y -= diffpitch;
cur.x += diffx*4.;
cur.y += diffy*4.;
return cur*SWWMUtility.Lerp(oldlagready,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) && (swwm_mutevoice < 2) )
{
failcooldown = SWWMHandler.AddOneliner("usefail",2,20);
failcounter = max(4,failcounter-10);
}
else if ( (failcounter > 2) && Random[DemoLines](0,1) && (gametic > failcooldown) && (swwm_mutevoice < 4) )
{
int loudlv = swwm_voiceamp;
A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
failcooldown = int(S_GetLength(String.Format("voice/%s/usegrunt",myvoice.GetString()))*GameTicRate);
failcounter = max(2,failcounter-1);
}
}
else if ( gametic > lastuse+50 ) failcounter = 0;
oldlagangle = lagangle;
oldlagpitch = lagpitch;
lagangle = lagangle*.8+angle*.2;
lagpitch = lagpitch*.8+pitch*.2;
if ( !player || (player.mo != self) ) return;
if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) )
{
// faster falloff
player.jumptics -= 5;
if ( player.onground && (player.jumptics < -18) )
player.jumptics = 0;
}
if ( (player.playerstate != PST_DEAD) && !ReactionTime )
{
// quick grenade
if ( player.cmd.buttons&BT_USER4 )
SWWMGesture.SetGesture(self,GS_Grenade);
// emergency melee with no weapon
else if ( !player.ReadyWeapon && (player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_USER1)) )
SWWMGesture.SetGesture(self,GS_EmptyMelee);
}
}
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;
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);
}
else continue;
Vector3 bpos = (0,0,16);
if ( t.bFLOATBOB ) bpos.z += t.GetBobOffset();
double alph = clamp((250000.-Distance3DSquared(t))/250000.,0.,1.);
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 = SWWMUtility.Vec3FromAngles(ang,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.);
}
}
bt.Destroy();
}
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 ( (gamestate != GS_LEVEL) || !player || (player.mo != self) ) return;
UpdateFace();
UpdateTags();
if ( hasteleported )
{
// we just got teleported, don't count the travel distance
oldpos = pos;
hasteleported = false;
}
if ( !selflight )
{
selflight = new("DemolitionistSelfLight");
selflight.ChangeStatNum(STAT_USER);
selflight.target = self;
selflight.Tick();
}
NearbyItemSparkles();
// double-check that we have these
if ( !FindInventory("AlmasteelPlating") )
{
let ap = Inventory(Spawn("AlmasteelPlating"));
if ( !ap.CallTryPickup(self) ) ap.Destroy();
}
if ( !FindInventory("SayaCollar") )
{
let sc = Inventory(Spawn("SayaCollar"));
if ( !sc.CallTryPickup(self) ) sc.Destroy();
}
// this is why we need mod cvar callbacks
if ( swwm_singlefirst != oldsinglefirst )
WeaponSlots.SetupWeaponSlots(self);
oldsinglefirst = swwm_singlefirst;
// overheal fading
if ( !isFrozen() && !(player.cheats&CF_TOTALLYFROZEN) )
{
if ( (health <= 200) || (health > oldhealth) )
{
healtimer = 0;
healcooldown = 80;
}
else if ( health > 200 )
{
if ( healcooldown > 0 ) healcooldown--;
else
{
if ( health > 1000 )
{
let spr = DivineSpriteEffect(FindInventory("DivineSpriteEffect"));
if ( !spr || spr.bHealDone )
A_SetHealth(max(1000,health-10));
if ( health <= 1000 ) healcooldown = 40;
}
else if ( health > 500 )
{
if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%3) )
A_SetHealth(health-1);
if ( health <= 500 ) healcooldown = 20;
}
else if ( health > 200 )
{
if ( !FindInventory("RefresherRegen") && !(healtimer%12) )
A_SetHealth(health-1);
}
healtimer++;
}
}
}
oldhealth = health;
oldlagvel = lagvel;
oldlagready = lagready;
if ( player.weaponstate&WF_WEAPONBOBBING ) lagready = lagready*.9+.1;
else lagready = lagready*.4;
lagvel = lagvel*.8+vel*.2;
double traveldist = level.Vec3Diff(oldpos,pos).length();
if ( !player.onground || bNoGravity )
{
if ( waterlevel > 1 )
{
cairtime = 0;
mystats.swimdist += traveldist;
}
else
{
cairtime++;
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
mystats.airdist += traveldist;
}
if ( (vel.z < -fallingscreamminspeed) && (vel.z > -fallingscreammaxspeed) && (player == players[consoleplayer]) )
SWWMHandler.AddOneliner("falling",2,30);
}
else
{
SWWMHandler.CancelOneliner("falling");
airscreamtime = 0;
cairtime = 0;
mystats.grounddist += traveldist;
SWWMUtility.AchievementProgressIncDouble("travel",traveldist/32000.,player);
}
// spawn bubbles while underwater
if ( (waterlevel > 1) && !Random[ExploS](0,5) )
{
int numpt = Random[ExploS](-2,2);
for ( int i=0; i<numpt; i++ )
{
let p = Spawn("SWWMBubble",Vec3Offset(FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](height*.1,height*.9)));
p.scale *= FRandom[ExploS](.02,.2);
p.vel += vel*.2;
}
}
CheckUnderwaterAmb();
SenseItems();
CheckItemMagnet();
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
if ( vel.length() > ((3600*GameTicRate)/32000.) )
SWWMUtility.AchievementProgress("sanic",int((vel.length()*3600*GameTicRate)/32000.),player);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( player.onground && !bNoGravity && !lastground )
{
// bump down weapon
bumpvelz.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 ( (lastvelz < -gruntspeed) && (swwm_mutevoice < 4) && (health > 0) )
{
int loudlv = swwm_voiceamp;
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
if ( lastvelz < -1 )
A_Footstep(0,1,clamp(-lastvelz*0.05,0.0,1.0),true);
// bounce off slopes
if ( pos.z <= floorz )
{
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 fnorm;
if ( ff ) fnorm = -ff.top.Normal;
else fnorm = FloorSector.floorplane.Normal;
double maxsteep = clamp(abs(lastvelz)*.1,0.,.9);
if ( fnorm.z < maxsteep ) vel = .8*fnorm*(-lastvelz)*(1.-.8*fnorm.z);
}
}
// crush anything we're standing on
bool dummy;
Actor encroached;
[dummy, encroached] = TestMobjZ();
// add special check so corpses don't get stuck on top of monsters and players
if ( encroached && encroached.bSOLID && (bSOLID || encroached.bACTLIKEBRIDGE) )
{
if ( (encroached is 'FroggyChair') && (encroached != oldencroached) )
encroached.A_StartSound("squeak",CHAN_BODY,CHANF_OVERLAP);
// try to follow movement (this method is awkward but works with monsters)
if ( encroached == oldencroached )
{
Vector3 oldp = pos;
Vector3 newp = level.Vec3Offset(pos,level.Vec3Diff(oldencroachedpos,encroached.pos));
if ( level.IsPointInLevel(newp) )
{
SetOrigin(newp,true);
if ( !TestMobjLocation() ) SetOrigin(oldp,true);
}
encroachtics++;
if ( !(encroachtics%GameTicRate) && (encroached.bISMONSTER || encroached.player || (encroached is 'ScriptedMarine')) && !IsFriend(encroached) )
SWWMUtility.AchievementProgress("step",encroachtics/GameTicRate,player);
}
else encroachtics = 0;
oldencroached = encroached;
oldencroachedpos = encroached.pos;
}
else
{
// launch in movement direction (useful for moving platforms)
// make sure we're not getting launched because an enemy just died under our feet, because that can cause some issues
if ( oldencroached && (dashboost <= 0.) && (lastvelz >= -25) && (!oldencroached.bISMONSTER || (oldencroached.Health > 0)) ) vel += oldencroached.vel+level.Vec3Diff(oldencroachedpos,oldencroached.pos);
oldencroached = null;
encroachtics = 0;
}
if ( encroached && encroached.bSHOOTABLE && !encroached.bNODAMAGE && (lastvelz <= 0) && !(encroached is 'Demolitionist') )
{
if ( (lastvelz < -5) || !(level.maptime%5) )
{
int realdmg = encroached.DamageMobj(self,self,int((2+max(0,-lastvelz*3))*max(1.,mass/encroached.mass)),'Jump',DMG_THRUSTLESS);
if ( FindInventory("RagekitPower") )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = realdmg;
}
if ( 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 = ((bFly&&!bFlyCheat&&!(player.cheats&CF_NOCLIP2))||isdashing);
if ( fuelcooldown == 1 ) A_StartSound("demolitionist/fuelregen",CHAN_FUELREGEN,CHANF_LOOP,.35,4.,.5);
else if ( fuelcooldown > 1 ) A_StopSound(CHAN_FUELREGEN);
fuelcooldown = max(0,fuelcooldown-1);
if ( dashlockst > 0 )
{
dashlockst--;
if ( dashlockst == 0 ) A_StartSound("demolitionist/dashregen",CHAN_BODY,CHANF_OVERLAP,.5,4.);
}
else dashcooldown = max(0,dashcooldown-1);
boostcooldown = max(0,boostcooldown-1);
if ( (fuelcooldown <= 0) && (dashfuel < default.dashfuel) )
{
A_SoundPitch(CHAN_FUELREGEN,.5+1.5*((dashfuel/default.dashfuel)**2.));
double oldfuel = dashfuel;
dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.));
// stops
if ( (oldfuel < (default.dashfuel/24)) && (dashfuel >= default.dashfuel/24) )
{
dashfuel = default.dashfuel/24;
fuelcooldown = 20;
A_StartSound("demolitionist/fuelrgstp",CHAN_BODY,CHANF_OVERLAP,.6,4.,.6);
}
else if ( (oldfuel < (default.dashfuel/12)) && (dashfuel >= default.dashfuel/12) )
{
dashfuel = default.dashfuel/12;
fuelcooldown = 10;
A_StartSound("demolitionist/fuelrgstp",CHAN_BODY,CHANF_OVERLAP,.6,4.,.7);
}
if ( (oldfuel < dashfuel) && (dashfuel == default.dashfuel) )
{
A_StopSound(CHAN_FUELREGEN);
A_StartSound("demolitionist/fuelrgend",CHAN_BODY,CHANF_OVERLAP,.6,4.);
}
}
else if ( dashfuel >= default.dashfuel ) A_StopSound(CHAN_FUELREGEN);
if ( ((dashboost <= 0) || !(isdashing || (isboosting && player.cmd.buttons&BT_JUMP))) && IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
PainChance = isdashing?0:255;
if ( isdashing || (vel.length() > 30) )
{
bool oldpush = bCANPUSHWALLS;
bool olduse = bCANUSEWALLS;
bool oldmcross = bACTIVATEMCROSS;
bool oldtele = bNOTELEPORT;
// needed to prevent the many TryMove calls from activating unwanted lines
bCANPUSHWALLS = false;
bCANUSEWALLS = false;
bACTIVATEMCROSS = false;
bNOTELEPORT = true;
Actor a;
if ( isdashing && (dashboost > 0.) )
{
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
{
a = Spawn("DashTrail",Vec3Angle(15,angle+i*140,35));
a.master = self;
a.vel = (RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
Vector3 dir = vel;
double spd = dir.length();
dir = dir/spd;
Vector3 viewdir = SWWMUtility.Vec3FromAngles(angle,pitch);
// look for things we could potentially bump into
bool bumped = false;
let bi = BlockThingsIterator.Create(self,500);
let raging = RagekitPower(FindInventory("RagekitPower"));
double maxmass = max(mass*spd/40.,200);
if ( raging ) maxmass *= 2;
while ( (spd > 0) && bi.Next() )
{
a = bi.Thing;
if ( !a || (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS || a.bCORPSE || !CanCollideWith(a,false) || !a.CanCollideWith(self,true) ) continue;
if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*(spd+radius),8) ) continue;
if ( (a.pos.z <= a.floorz) && (a.height <= MaxStepHeight) ) continue;
Vector3 diff = level.Vec3Diff(pos,a.pos);
Vector3 dirto = diff.unit();
if ( dir dot dirto < .1 ) continue;
if ( (diff.z <= -a.height) && (lastvelz < -25) ) continue;
if ( (diff.z <= -a.height) && !isdashing ) continue;
// don't bump bridges if hit at a specific angle
if ( a.bACTLIKEBRIDGE )
{
Vector3 bnorm = -dirto;
if ( diff.z <= -a.height ) continue; // no bump from above
else if ( diff.z >= Height ) bnorm = (0,0,-1);
else if ( diff.x > a.Radius+Radius ) bnorm = (-1,0,0);
else if ( diff.x < -(a.Radius+Radius) ) bnorm = (1,0,0);
else if ( diff.y > a.Radius+Radius ) bnorm = (0,-1,0);
else if ( diff.y < -(a.Radius+Radius) ) bnorm = (0,1,0);
if ( dir dot bnorm > -.6 ) continue;
}
if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
// large monsters will stop the player (unless hit from above if we're going at ground pound speed)
A_QuakeEx(4,4,4,10,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_AlertMonsters(swwm_uncapalert?0:800);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
a.A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(20+spd/4.);
lastbump *= .8;
if ( (a.bDONTTHRUST || a.bACTLIKEBRIDGE || (a.Mass >= maxmass) || (!a.bSHOOTABLE && !a.bPUSHABLE && (a.Health > 0))) && a.bSOLID && (dir dot dirto > .65) )
{
if ( bumped ) continue;
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel -= dirto*(10+(spd*50/mass));
vel.z += 5+(spd*(10/mass));
dashboost *= 0.;
}
Vector3 pushdir = dirto*.1+dir*.9;
if ( !a.bDONTTHRUST && (a.Mass < maxmass) && (a.bSHOOTABLE || a.bPUSHABLE) )
{
a.vel += pushdir*(25+(spd*20/max(50,a.mass)));
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
a.vel.z += 5+(spd*(5/max(50,a.mass)));
}
int flg = DMG_THRUSTLESS;
if ( raging ) flg |= DMG_FOILINVUL;
if ( !a.player && !a.bDONTBLAST ) a.bBLASTED = true;
int dmg = int(10+spd*3.);
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
dmg *= 3;
buttslam = true;
}
if ( a.bSHOOTABLE )
{
dmg = a.DamageMobj(self,self,dmg,buttslam?'Buttslam':'Dash',flg);
if ( a && !a.bNOBLOOD && (raging || !a.bINVULNERABLE) )
{
a.TraceBleed(dmg,self);
a.SpawnBlood(level.Vec3Offset(pos,diff/2),atan2(dir.y,dir.x)+180,dmg);
}
if ( buttslam && (!a || (a.Health <= 0)) )
{
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
Spawn("SWWMItemFog",level.Vec3Offset(pos,diff/2));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
}
if ( raging )
{
let ps = Spawn("BigPunchSplash",level.Vec3Offset(pos,diff/2));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = dmg;
raging.DoHitFX();
}
}
// check for ceiling collision
if ( (spd > 0) && !bumped && ((pos.z+Height+dir.z*spd) >= ceilingz) )
{
F3DFloor ff;
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 ceilnorm;
if ( ff ) ceilnorm = ff.model.floorplane.Normal;
else ceilnorm = CeilingSector.ceilingplane.Normal;
// don't bump if we're only grazing it
if ( dir dot ceilnorm < -.6 )
{
bool busted = false;
if ( raging || swwm_omnibust )
{
// see if we can bust it
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = int(10+spd*3.);
if ( raging ) dmg *= 8;
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
dmg *= 3;
buttslam = true;
}
if ( ff ) tempme.Results.ffloor = ff;
tempme.Results.HitSector = CeilingSector;
tempme.Results.HitType = TRACE_HitCeiling;
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
{
// busted through
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
busted = true;
if ( buttslam )
{
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
Spawn("SWWMItemFog",Vec3Offset(0,0,Height));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
}
}
if ( !busted || !raging )
{
// headbump
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(20+spd/4.);
lastbump *= .8;
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_AlertMonsters(swwm_uncapalert?0:800);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel.z += 5+(spd*(10/mass));
vel += ceilnorm*(15+(spd*40/mass));
dashboost *= 0.;
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
}
}
}
// check for wall collision
FCheckPosition tm;
Vector2 steppy = dir.xy*spd/8.;
Vector3 oldpos = pos;
bool oldthru = bTHRUACTORS;
bTHRUACTORS = true;
for ( int i=1; i<=8; i++ )
{
if ( (spd > 0) && !bumped && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) && SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKEVERYTHING|Line.ML_BLOCKING|Line.ML_BLOCK_PLAYERS) )
{
Vector3 wallnorm = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
int lside = 1;
if ( !BlockingLine.sidedef[1] || !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
{
lside = 0;
wallnorm *= -1;
}
// don't bump if we're only grazing it
if ( dir dot wallnorm > -.6 )
continue;
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
buttslam = true;
// leave buttmark
Vector3 vdir = (2,0,Height*.54);
A_SprayDecal("ButtMark",172,SWWMUtility.RotateVector3(vdir,angle+90),dir);
A_SprayDecal("ButtMark",172,SWWMUtility.RotateVector3(vdir,angle-90),dir);
}
if ( raging || swwm_omnibust )
{
// see if we can bust it
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = int(10+spd*3.);
if ( raging ) dmg *= 8;
if ( buttslam ) dmg *= 3;
tempme.Results.HitLine = BlockingLine;
tempme.Results.HitType = TRACE_HitWall;
tempme.Results.Side = lside;
tempme.Results.Tier = TIER_MIDDLE;
if ( BlockingLine.sidedef[1] )
{
double ceilz = BlockingLine.sidedef[!lside].sector.ceilingplane.ZAtPoint(pos.xy);
double florz = BlockingLine.sidedef[!lside].sector.floorplane.ZAtPoint(pos.xy);
if ( pos.z+Height >= ceilz )
tempme.Results.Tier = TIER_UPPER;
else if ( pos.z <= florz )
tempme.Results.Tier = TIER_LOWER;
}
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
{
// busted through
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
if ( buttslam )
{
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
Spawn("SWWMItemFog",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
if ( raging ) continue; // don't stop
}
}
// wallbump
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(25+spd/4.);
lastbump *= .8;
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_AlertMonsters(swwm_uncapalert?0:800);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel += wallnorm*(10+(spd*50/mass));
vel.z += 5+(spd*(10/mass));
dashboost *= 0.;
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,Height/2.));
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
// activate it
int locknum = SWWMUtility.GetLineLock(BlockingLine);
if ( !locknum || CheckKeys(locknum,false,true) )
{
hitactivate = true;
BlockingLine.Activate(self,lside,SPAC_Use);
hitactivate = false;
}
BlockingLine.Activate(self,lside,SPAC_Impact);
break;
}
}
// check for slope boosting (only if dashing)
if ( (pos.z <= floorz) && (spd > 0) && isdashing )
{
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 fnorm;
if ( ff ) fnorm = -ff.top.Normal;
else fnorm = FloorSector.floorplane.Normal;
// how far up should the boost be
double xspd = spd*(1.-dir.z);
vel.z += xspd*(1.-fnorm.z);
}
SetOrigin(oldpos,true);
bTHRUACTORS = oldthru;
bCANPUSHWALLS = oldpush;
bCANUSEWALLS = olduse;
bACTIVATEMCROSS = oldmcross;
bNOTELEPORT = oldtele;
}
else if ( isboosting && (dashboost > 0.) )
{
Actor a;
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
{
a = Spawn("DashTrail",Vec3Angle(10,angle+i*140,40));
a.master = self;
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
}
private void CheckBreakCrusher()
{
double gaph = (ceilingz-floorz);
if ( gaph > height*.8 ) return;
// the smaller the gap, the more likely the crusher will snap
if ( Random[Demolitionist](0,2) && (FRandom[Demolitionist](0,gaph/height) > .2) ) return;
double diffh = 8.+(default.height-gaph); // how much the crusher will have to "snap" after breaking
let ceil = ceilingsector;
let flor = floorsector;
let ceilse = ceil.ceilingdata;
let florse = flor.floordata;
if ( ceilse && florse )
{
// snap both planes
let q = Spawn("BustedQuake",(ceil.centerspot.x,ceil.centerspot.y,ceil.ceilingplane.ZAtPoint(ceil.centerspot)));
q.special1 = 6;
q = Spawn("BustedQuake",(flor.centerspot.x,flor.centerspot.y,flor.floorplane.ZAtPoint(flor.centerspot)));
q.special1 = 6;
SWWMCrusherBroken.Create(flor,ceil,diffh/2.);
}
else if ( ceilse )
{
// snap ceiling
let q = Spawn("BustedQuake",(ceil.centerspot.x,ceil.centerspot.y,ceil.ceilingplane.ZAtPoint(ceil.centerspot)));
q.special1 = 10;
SWWMCrusherBroken.Create(null,ceil,diffh);
}
else if ( florse )
{
// snap floor
let q = Spawn("BustedQuake",(flor.centerspot.x,flor.centerspot.y,flor.floorplane.ZAtPoint(flor.centerspot)));
q.special1 = 10;
SWWMCrusherBroken.Create(flor,null,diffh);
}
SWWMUtility.MarkAchievement("crush",player);
}
private void CheckBreakPolyobject( int dmg )
{
// see if there are any crushing polyobjects currently "encroaching" the player
Array<Line> touching;
BlockLinesIterator bl = BlockLinesIterator.Create(self,radius+8);
double tbox[4];
// top, bottom, left, right
tbox[0] = pos.y+(radius+8);
tbox[1] = pos.y-(radius+8);
tbox[2] = pos.x-(radius+8);
tbox[3] = pos.x+(radius+8);
while ( bl.Next() )
{
Line l = bl.CurLine;
if ( !l ) continue;
if ( tbox[2] > l.bbox[3] ) continue;
if ( tbox[3] < l.bbox[2] ) continue;
if ( tbox[0] < l.bbox[1] ) continue;
if ( tbox[1] > l.bbox[0] ) continue;
if ( SWWMUtility.BoxOnLineSide(tbox[0],tbox[1],tbox[2],tbox[3],l) != -1 ) continue;
touching.Push(l);
}
let pi = swwm_PolyobjectIterator.Create();
swwm_PolyobjectHandle p;
while ( p = pi.Next() )
{
if ( (p.Type != swwm_PolyobjectHandle.POTYP_CRUSH) && (p.Type != swwm_PolyobjectHandle.POTYP_HURT) )
continue;
for ( int i=0; i<touching.Size(); i++ )
{
if ( p.Lines.Find(touching[i]) >= p.Lines.Size() ) continue;
Vector2 diragainst = pos.xy-p.GetPos();
double vsiz = diragainst.length();
if ( vsiz > 0 ) diragainst /= vsiz;
if ( BusterWall.BustPolyobj(p,max(dmg,(100-health)*5),self,(diragainst.x,diragainst.y,0)) )
SWWMUtility.MarkAchievement("crush",player);
if ( p.Mirror && BusterWall.BustPolyobj(p.Mirror,max(dmg,(100-health)*5),self,-(diragainst.x,diragainst.y,0)) )
SWWMUtility.MarkAchievement("crush",player);
}
}
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// we still have to ENSURE ENTIRELY that this gets nullified (TELEFRAG_DAMAGE overrides damage factors somehow)
if ( (mod == 'Falling') | (mod == 'Drowning') || (mod == 'Poison') || (mod == 'PoisonCloud') )
damage = 0;
if ( (swwm_strictuntouchable >= 2) && (damage > 0) && player )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd ) hnd.tookdamage[PlayerNumber()] = true;
}
if ( (mod == 'Crush') && player && (player.mo == self) )
{
// check if we can break any active crushers
// (or polyobjects)
if ( !inflictor && !source )
{
CheckBreakCrusher();
CheckBreakPolyobject(damage);
}
// break a spike trap
else if ( source is 'ThrustFloor' )
{
let q = Spawn("BustedQuake",source.pos);
q.special1 = 4;
int numpt = Random[ExploS](30,40);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("SWWMChip",source.Vec3Angle(source.radius,FRandom[ExploS](0,360),1.+FRandom[ExploS](0,max(0.,source.height-source.floorclip))));
s.vel = ((0,0,1)+SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*.6).unit()*FRandom[ExploS](2.,16.);
s.scale *= FRandom[ExploS](1.5,3.);
s.A_SetTranslation('StoneSpike');
}
numpt = Random[ExploS](12,16);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("SWWMHalfSmoke",source.Vec3Offset(0,0,1.+FRandom[ExploS](0,max(0.,source.height-source.floorclip))));
s.vel = ((0,0,1)+SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))).unit()*FRandom[ExploS](-2,8.);
s.scale *= 2.5;
s.special1 = Random[ExploS](3,8);
s.SetShade(Color(5,4,3)*Random[ExploS](20,40));
}
Spawn("SWWMCrushedSpike",source.pos);
if ( source.master ) source.master.Destroy();
source.Destroy();
damage = 20; // reduce so it's not instant kill
SWWMUtility.MarkAchievement("kancho",player);
}
}
// no damage whatsoever
if ( scriptedinvul )
return 0;
if ( damage <= 0 ) return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
if ( !inflictor && !source && (FloorSector.flags&Sector.SECF_ENDLEVEL) )
{
// end level hax
damage = max(50,health-100);
flags |= DMG_FORCED|DMG_NO_ARMOR;
mod = 'EndLevel';
}
int oldpchance = PainChance;
if ( damage < 5 ) PainChance = 0;
int realdmg = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
if ( lastdamagetic != gametic )
{
lastdamage = 0;
lastdamagetimer = 0;
}
lastdamage += realdmg;
lastdamagetic = gametic;
lastdamagetimer = max(lastdamagetimer,lastdamagetic+clamp(lastdamage/2,10,40));
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
{
lastmpain = level.maptime;
if ( player && (player.mo == self) ) A_DemoPain();
}
PainChance = oldpchance;
if ( (Health <= 0) && (source == self) && (flags&DMG_EXPLOSION) )
SWWMUtility.MarkAchievement("dime",player);
facedamage = true;
return realdmg;
}
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 ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
return; // handled in moveplayer
Super.CheckPitch();
}
override void CheckMoveUpDown()
{
if ( InStateSequence(CurState,FindState("Dash")) )
player.cmd.upmove = 0;
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
double fs = TweakSpeed();
Vector3 x, y, z;
[x, y, z] = 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();
}
private bool ShouldDecelerate( Sector s )
{
// check if we can apply fast decel while standing on this sector
// (important to not break certain intended vanilla effects)
if ( bNOFRICTION )
return false; // we don't have friction at all (e.g.: while dashing)
if ( bWINDTHRUST && (s.special >= 40) && (s.special <= 51) )
return false; // wind
if ( (s.special == 84) || (s.special == 118) || (s.special >= 201) && (s.special <= 244) )
return false; // current
return true;
}
override void MovePlayer()
{
if ( !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
{
dashboost = 0.;
player.vel *= 0.;
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
Super.MovePlayer();
return;
}
bool isdashing = InStateSequence(CurState,FindState("Dash"));
if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0;
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
player.onground = false;
if ( player.turnticks )
{
player.turnticks--;
angle += (180./TURN180_TICKS);
}
else guideangle += .2*player.cmd.yaw*(360./65536.);
guidepitch -= .2*player.cmd.pitch*(360./65536.);
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
guideroll = clamp(deltaangle(roll,0),-3.,3.);
A_SetAngle(angle+guideangle,SPF_INTERPOLATE);
A_SetPitch(clamp(pitch+guidepitch,player.MinPitch,player.MaxPitch),SPF_INTERPOLATE);
A_SetRoll(roll+guideroll,SPF_INTERPOLATE);
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
{
guideroll = guidepitch = roll = pitch = 0.;
player.centering = false;
}
double fs = TweakSpeed();
double jcmove = 0.;
if ( player.cmd.buttons&BT_JUMP ) jcmove += 4096.;
if ( player.cmd.buttons&BT_CROUCH ) jcmove -= 4096.;
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 x, y, z;
[x, y, z] = 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 *= .95;
player.vel = (1,1)*vel.length();
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) && (player.cmd.forwardmove|player.cmd.sidemove) )
PlayRunning();
if ( player.cheats&CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
else
{
if ( player.turnticks )
{
player.turnticks--;
angle += (180./TURN180_TICKS);
}
else angle += player.cmd.yaw*(360./65536.);
player.onground = (pos.z<=floorz)||bOnMobj||bMBFBouncer||(player.cheats&CF_NOCLIP2);
if ( player.cmd.forwardmove|player.cmd.sidemove )
{
double bobfactor;
double friction, movefactor;
[friction, movefactor] = GetFriction();
bobfactor = (friction<ORIG_FRICTION)?movefactor:ORIG_FRICTION_FACTOR;
if ( !player.onground && !bNoGravity )
{
// no air control here, done afterwards
movefactor *= 0.;
bobfactor *= 0.;
}
// use normalized movement vector, no SR40 (not that we need it with how fast we can run)
Vector2 nmove = NormalizedMove();
nmove *= TweakSpeed();
nmove *= Speed/256.;
// When crouching, speed and bobbing have to be reduced
if ( CanCrouch() && (player.crouchfactor != 1) )
{
nmove *= player.crouchfactor;
bobfactor *= player.crouchfactor;
}
nmove *= movefactor;
if ( bNOGRAVITY && !player.GetClassicFlight() )
{
double zpush = nmove.x*sin(pitch);
vel.z -= zpush;
vel.xy += RotateVector(nmove,angle)*cos(pitch);
player.vel += RotateVector(nmove,angle)*bobfactor*cos(pitch)*16.;
}
else
{
vel.xy += RotateVector(nmove,angle);
player.vel += RotateVector(nmove,angle)*bobfactor*16.;
}
// override air control because we REALLY need the extra mobility
if ( !player.onground && !bNOGRAVITY )
{
nmove = NormalizedMove();
double fs = TweakSpeed();
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
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 && ShouldDecelerate(floorsector) )
{
// quickly decelerate if we're not holding movement keys
// (account for slippery floors, and assume approx .9 friction as "midpoint" for 85% reduction)
double fact = clamp(floorsector.GetFriction(0)*.95,.5,1.);
vel *= fact;
player.vel *= fact;
}
if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE);
}
guideangle *= .9;
guidepitch *= .9;
guideroll *= .9;
// anchor to ground when going down steps
if ( lastground && !player.onground && !bFly && !bFlyCheat && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z <= 0) && !isdashing )
{
// test for gap crossing (i.e: climbing up platforms with holes between them)
Vector3 storepos = pos;
double storefloorz = floorz;
bool crossgap = false;
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
{
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),true);
if ( floorz < storepos.z ) continue;
crossgap = true;
break;
}
SetOrigin(storepos,true);
floorz = storefloorz;
if ( !crossgap )
{
ssup = max(0,(pos.z-floorz));
SetZ(floorz);
lastground = player.onground = true;
}
}
if ( player.onground ) lastgroundtic = level.maptime;
else lastairtic = level.maptime;
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
PlayIdle();
Vector3 dodge = (0,0,0), x, y, z;
[x, y, z] = 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 || (player.cheats&CF_NOCLIP2) ) dodge = X;
else dodge.xy = RotateVector((1,0),angle);
}
if ( player.onground && !bNOGRAVITY && !(player.cheats&CF_NOCLIP2) )
{
dodge.z = max(0,dodge.z);
if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z);
}
if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && (player.onground || level.IsJumpingAllowed() || (player.cmd.buttons&BT_CROUCH)) && (gamestate == GS_LEVEL) )
{
fullfuel = (dashfuel >= default.dashfuel);
dashdir = dodge.unit();
dashcooldown = 10;
dashboost = 20.;
bOnMobj = false;
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
vel += dashdir*dashboost;
vel.z += clamp(-vel.z*.4,0.,30.);
player.jumptics = -1;
SetStateLabel("Dash");
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
lastbump *= .95;
mystats.dashcount++;
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 = AngleToVector(angle);
bool walljump = false, wallclimb = false;
double climbvelz = 0.;
FLineTraceData d;
Actor jumpactor = null;
for ( int i=-4; i<int(height*.8); i++ )
{
if ( !walljump )
{
for ( int k=0; k<60; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = (angle-180)+k*j;
walljump = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( walljump )
{
jumpactor = d.HitActor;
walldir = (walldir*.7+AngleToVector(ang+180)*.3);
break;
}
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( walljump ) break;
if ( !wallclimb )
{
for ( int k=0; k<30; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = angle+k*j;
wallclimb = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( wallclimb )
{
// double-check that it's climbable
if ( d.HitType == TRACE_HitWall )
{
if ( !d.HitLine.sidedef[1] || !!(d.HitLine.flags&(Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING|Line.ML_BLOCK_PLAYERS)) ) // nope
{
wallclimb = false;
walljump = true;
walldir = -(walldir*.7+AngleToVector(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+AngleToVector(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+AngleToVector(ang)*.3);
break;
}
climbvelz = 1.5*sqrt(max(1.,(d.HitActor.pos.z+d.HitActor.Height)-pos.z));
}
jumpactor = d.HitActor;
walldir = (walldir*.7+AngleToVector(ang)*.3);
break;
}
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
if ( level.maptime < (lastgroundtic+4) )
{
walljump = false;
wallclimb = false;
jumpactor = null;
}
if ( player.crouchoffset ) player.crouching = 1;
else if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return;
else if ( bNoGravity ) vel.z = 3;
else if ( level.IsJumpingAllowed()
&& ((player.onground && (player.jumptics == 0))
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) )
{
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
return;
double jumpvelz = JumpZ*35./GameTicRate;
double jumpfac = 0;
for ( let p=Inv; p; p=p.Inv )
{
let pp = PowerHighJump(p);
if ( pp )
{
double f = pp.Strength;
if ( f > jumpfac ) jumpfac = f;
}
}
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
bool raging = FindInventory("RagekitPower");
if ( raging ) jumpvelz *= 2.;
double pvelz = vel.z;
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
{
// check for wall stuff
if ( walljump )
{
if ( vel.z < 10. )
vel.z += 2.*jumpvelz+clamp(-pvelz*.6,0.,30.);
vel.xy += walldir*20*Speed;
lastbump *= .95;
}
else if ( wallclimb )
{
if ( vel.z < 10. )
vel.z += climbvelz+clamp(-pvelz*.95,0.,30.);
vel.xy += walldir*10*Speed;
lastbump *= .97;
}
if ( walljump && jumpactor && jumpactor.bSHOOTABLE )
{
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
if ( raging )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = dmg;
}
}
if ( walljump || wallclimb )
{
last_kick = level.maptime+1;
SWWMUtility.AchievementProgressInc("jump",1,player);
}
}
bOnMobj = false;
player.jumpTics = -1;
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
{
dashboost = 3.;
boostcooldown = 20;
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
lastbump *= .95;
mystats.boostcount++;
last_boost = level.maptime+1;
double newp = min(90.,pitch+3.);
bumppitch.Push(newp-pitch);
A_SetPitch(newp,SPF_INTERPOLATE);
SetStateLabel("Boost");
}
else
{
A_StartSound(walljump?"demolitionist/kick":"demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
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("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();
}
}
if ( swwm_mutevoice < 4 )
{
int loudlv = swwm_voiceamp;
A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
SetStateLabel("Jump");
}
}
last_jump_held = level.maptime+1;
}
override void DeathThink()
{
player.Uncrouch();
TickPSprites();
player.onground = (pos.Z<=floorz);
// ded (demo-chan falls faster tho)
player.deltaviewheight = 0;
if ( player.viewheight > 6 ) player.viewheight -= 3;
if ( player.viewheight < 6 ) player.viewheight = 6;
// center pitch
double dpitch = clamp(deltaangle(pitch,0),-6,6);
if ( abs(dpitch) < 3. ) pitch = 0.;
else A_SetPitch(pitch+dpitch,SPF_INTERPOLATE);
// add roll
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
if ( abs(droll) < 2. ) roll = 50.;
else A_SetRoll(roll+droll,SPF_INTERPOLATE);
player.mo.CalcHeight();
if ( player.damagecount ) player.damagecount--;
if ( player.poisoncount ) player.poisoncount--;
// solid unless we can respawn, for safety
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
bSolid = false;
else bSolid = true;
if ( player.viewheight <= 6 )
{
deadtimer++;
if ( (deadtimer == 60) && (player == players[consoleplayer]) )
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI);
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (((player.cmd.buttons&BT_USE) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn)
&& ((Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot)) )
{
player.cls = null;
player.playerstate = PST_REBORN;
if ( special1 > 2 ) special1 = 0;
}
}
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) )
{
// reload save
player.cls = null;
player.playerstate = PST_ENTER;
if ( special1 > 2 ) special1 = 0;
}
else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 120) && !FindInventory("SWWMReviveDisabler") && swwm_revive )
{
// reboot (if possible)
if ( !FindInventory("ReviveCooldown") && (((swwm_revivecooldown >= 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4)) || !hasrevived) )
{
if ( hasrevived ) SWWMUtility.MarkAchievement("sekiro",player);
hasrevived = true;
player.Resurrect();
player.damagecount = 0;
player.bonuscount = 0;
player.poisoncount = 0;
blinktime = 30;
SetState(FindState("Spawn")+1); // skip tweening
roll = 0;
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = 30;
ReactionTime = 17;
A_Stop();
A_AlertMonsters(swwm_uncapalert?0:2500);
if ( player == players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
}
lastbump *= 1.5;
SWWMHandler.DoFlash(self,Color(255,255,255,255),10);
SWWMHandler.DoFlash(self,Color(255,128,192,255),30);
if ( special1 > 2 ) special1 = 0;
if ( (swwm_revivecooldown > 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4) )
GiveInventory("ReviveCooldown",1);
}
else if ( level.maptime > revivefail )
{
if ( player == players[consoleplayer] ) A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI);
revivefail = level.maptime+120;
}
}
}
else deadtimer = 0;
}
override void PlayIdle()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("Spawn")) )
SetStateLabel("Spawn");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("Crouch");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("Float")) )
{
SetStateLabel("Spawn");
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
}
else if ( InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop")) )
SetStateLabel("SeeFastEnd");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
}
}
else if ( !bNoGravity )
{
// Falling
if ( player.crouchdir == -1 )
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
}
else
{
if ( (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float")))
&& (abs(pos.z-floorz) > maxstepheight) )
SetStateLabel("Fall");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
}
}
else
{
// Swimming
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop")) )
SetStateLabel("Float");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
}
}
}
override void PlayRunning()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("SeeRun")) )
SetStateLabel("SeeRun");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
{
switch( FastCheck() )
{
case 2:
SetStateLabel("CrouchMoveFast");
break;
case 1:
SetStateLabel("CrouchMoveRun");
break;
default:
SetStateLabel("CrouchMove");
break;
}
}
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
else if ( (FastCheck() == 2)
&& (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))) )
SetStateLabel("SeeFast");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Turn")) )
{
if ( FastCheck() == 1 ) SetStateLabel("SeeRun");
else SetStateLabel("See");
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
}
}
}
else if ( !bNoGravity )
{
// Falling
PlayIdle();
}
else
{
// Swimming
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
{
switch( FastCheck() )
{
case 2:
SetStateLabel("CrouchMoveFast");
break;
case 1:
SetStateLabel("CrouchMoveRun");
break;
default:
SetStateLabel("CrouchMove");
break;
}
}
}
else if ( bFlyCheat || (player.cheats&CF_NOCLIP2) )
{
// Special case, fly cheats don't play a swim animation, only float
// (this fixes Demo "swimming" on the library ladder in Spooktober, for example)
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("Float");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float")) )
SetStateLabel("Swim");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
}
}
}
override void PlayAttacking()
{
// Do nothing if it's a SWWM weapon, since those do things themselves
if ( player && (player.ReadyWeapon is 'SWWMWeapon') )
return;
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
override void PlayAttacking2()
{
PlayAttacking();
}
void PlayFire()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
void PlayMelee()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMelee");
else SetStateLabel("Melee");
}
void PlayFastMelee()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastMelee");
else SetStateLabel("FastMelee");
}
void PlayReload()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchReload");
else SetStateLabel("Reload");
}
void PlayFastReload()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastReload");
else SetStateLabel("FastReload");
}
void PlayCheckGun()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchCheckGun");
else SetStateLabel("CheckGun");
}
void A_DMFade()
{
if ( player ) return;
Spawn("TeleportFog",pos,ALLOW_REPLACE);
Destroy();
}
void A_DemoPain()
{
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
int loudlv = swwm_voiceamp;
if ( lastdamage > 70 )
{
A_QuakeEx(3,3,3,9,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_StartSound("demolitionist/hipain",CHAN_VOICE);
lastbump *= 1.04;
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
else if ( lastdamage > 30 )
{
A_QuakeEx(2,2,2,6,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_StartSound("demolitionist/pain",CHAN_VOICE);
lastbump *= 1.02;
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
else if ( lastdamage > 0 )
{
A_QuakeEx(1,1,1,3,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_StartSound("demolitionist/lopain",CHAN_VOICE);
lastbump *= 1.01;
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
}
void A_DemoScream()
{
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
A_StopSound(CHAN_DEMOVOICE);
A_StopSound(CHAN_DEMOVOICEAUX);
A_StopSound(CHAN_DEMOVOICEAUX2);
A_StopSound(CHAN_DEMOVOICEAUX3);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
int loudlv = swwm_voiceamp;
Sound snd = "demolitionist/death";
if ( special1 < 10 )
snd = "demolitionist/wdeath";
if ( health < -50 )
snd = "demolitionist/xdeath";
A_StartSound(snd,CHAN_VOICE);
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
override bool OnGiveSecret( bool printmsg, bool playsound )
{
if ( !player ) return false;
int score = 100;
// last secret (this is called before counting it up, so have to subtract)
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !deathmatch && !(gameinfo.gametype&GAME_Hexen) && (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
{
if ( hnd ) hnd.allsecrets = true;
score = 1000;
if ( player == players[consoleplayer] )
{
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),score);
SWWMHandler.AddOneliner("findsecret",2,40);
facegrin = true;
}
else Console.Printf(StringTable.Localize("$SWWM_LASTSECRETREM"),player.GetUserName(),score);
SWWMUtility.AchievementProgressInc("allsecrets",1,player);
}
else if ( player == players[consoleplayer] )
{
Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),score);
SWWMHandler.AddOneliner("findsecret",2,40);
facegrin = true;
}
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRETREM"),player.GetUserName(),score);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(score,Vec3Offset(0,0,Height/2));
// somehow ongivesecret can be called BEFORE PostBeginPlay (what the fuck)
if ( !mystats ) mystats = SWWMStats.Find(player);
mystats.secrets++;
return true;
}
override void RemoveInventory( Inventory item )
{
// stop any looping sounds our current weapon had
if ( (item is 'SWWMWeapon') && player && (SWWMWeapon(item) == player.ReadyWeapon) )
{
A_StopSound(CHAN_WEAPONEXTRA);
A_StopSound(CHAN_WEAPONEXTRA2);
A_StopSound(CHAN_WEAPONEXTRA3);
}
Super.RemoveInventory(item);
}
override void AddInventory( Inventory item )
{
Super.AddInventory(item);
if ( !player ) return;
String cn = item.GetClassName();
if ( !bInDefaultInventory && (level.maptime > 0) && (!(item is 'SWWMWeapon') || !(cn.Left(4) ~== "Dual")) ) // dual weapons are ignored here, as they're handled separately
{
// add lore if any
SWWMLoreLibrary.Add(player,cn);
// weapon get oneliner
if ( (item is 'Weapon') && !(item is 'SWWMGesture') && !(item is 'SWWMItemGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && (player == players[consoleplayer]) && !ingivecheat )
{
if ( (item is 'HeavyMahSheenGun') && !Random[DemoLines](0,2) && SWWMHandler.AddOneliner("sheenspecial",2,20) )
{
A_StartSound("sheen/specialpick",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
A_StartSound("sheen/specialpick",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
}
else if ( (item is 'SWWMWeapon') && (SWWMWeapon(item).GetLine != "") )
{
// fall back to generic weapon get if voicepack lacks weapon-specific lines
if ( !SWWMHandler.AddOneliner(SWWMWeapon(item).GetLine,2) )
SWWMHandler.AddOneliner("getweapon",2);
}
else SWWMHandler.AddOneliner("getweapon",2);
facegrin = true;
}
}
if ( (item is 'Key') && !key_reentrant && !deathmatch && !bInDefaultInventory )
{
// score
int score = 100;
if ( player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),item.GetTag(),score);
else Console.Printf(StringTable.Localize("$SWWM_FINDKEYREM"),player.GetUserName(),item.GetTag(),score);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(100,player.mo.Vec3Offset(0,0,Height/2));
if ( !ingivecheat )
{
if ( !Random[DemoLines](0,5) &&
((item.GetClass() == 'RedSkull') || (item.GetClass() == 'YellowSkull')
|| (item.GetClass() == 'BlueSkull') || (item.GetClassName() == "PurpleSkull")) )
{
if ( !SWWMHandler.AddOneliner("skullget",2) )
SWWMHandler.AddOneliner("keyget",2);
}
else SWWMHandler.AddOneliner("keyget",2);
facegrin = true;
}
// share all keys in mp
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo || (i == PlayerNumber()) )
continue;
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = true;
let tkey = Inventory(Spawn(item.GetClass()));
SWWMHandler.KeyTagFix(tkey);
if ( !tkey.CallTryPickup(players[i].mo) ) tkey.Destroy();
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = false;
}
}
// add collectible to stats
if ( item is 'SWWMCollectible' )
{
if ( !ingivecheat )
{
SWWMHandler.AddOneliner(SWWMCollectible(item).GetLine,2);
facegrin = true;
}
if ( !mystats ) return;
let cls = item.GetClass();
if ( (mystats.ownedcollectibles.Size() > 0) && (mystats.ownedcollectibles.Find(cls) < mystats.ownedcollectibles.Size()) ) return;
mystats.ownedcollectibles.Push(cls);
}
// yorick
if ( (item is 'PuzzSkull') && mystats && !mystats.gotyorick && !ingivecheat )
{
mystats.gotyorick = true;
SWWMHandler.AddOneliner("skullget",2);
facegrin = true;
}
// notify key obtained to flash icon
if ( item is 'Key' )
Console.MidPrint(null,"swwmkeyget."..item.GetClassName());
}
override bool UseInventory( Inventory item )
{
let itemtype = item.GetClass();
if ( (player.cheats&CF_TOTALLYFROZEN) || isFrozen() ) return false;
// do the key gesture
if ( item is 'Key' )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd ) hnd.equinoxhack = true;
let rep = (Class<SWWMKey>)(GetReplacement(item.GetClass()));
if ( hnd ) hnd.equinoxhack = false;
if ( rep )
{
SWWMGesture.SetSpecialGesture(self,GetDefaultByType(rep).gesture);
return false;
}
}
if ( !Actor.UseInventory(item) )
{
if ( player == players[consoleplayer] )
{
if ( !(item is 'Weapon') && !(item is 'SWWMCollectible') )
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.target = self;
b.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/3600.)*TweakSpeed();
}
else if ( run == 1 )
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
if ( !nosplash )
{
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/4800.)*TweakSpeed();
}
else
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.2);
if ( !nosplash )
{
let b = Spawn("SmolInvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/6400.)*TweakSpeed();
}
}
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) && 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 PreTravelled()
{
// clean up attached actors
if ( selflight ) selflight.Destroy();
// clean up our objects
SWWMItemSense si = itemsense;
while ( si )
{
let next = si.next;
si.Destroy();
si = next;
}
SWWMMagItem mi = magitem;
while ( mi )
{
let next = mi.next;
mi.Destroy();
mi = next;
}
magitem_cnt = 0;
// disable death exits
if ( player && (player.playerstate == PST_DEAD) )
{
player.cheats &= ~(CF_FROZEN|CF_TOTALLYFROZEN);
player.Resurrect();
player.damagecount = 0;
player.bonuscount = 0;
player.poisoncount = 0;
roll = 0;
if ( special1 > 2 ) special1 = 0;
}
// inventory wipes
if ( invwipe && (player.playerstate != PST_DEAD) )
{
bool wiped = false;
bool resetammo = false;
bool resetitems = false;
bool resethealth = false;
if ( invwipe&WIPE_EPISODE )
{
SWWMUtility.WipeInventory(self,swwm_resetscore);
wiped = true;
}
if ( invwipe&WIPE_CLUSTER )
{
if ( (swwm_ps_fullreset == 2) && !wiped )
{
SWWMUtility.WipeInventory(self,swwm_resetscore);
wiped = true;
}
if ( (swwm_ps_resetammo == 2) && !wiped )
{
SWWMUtility.ResetAmmo(self);
resetammo = true;
}
if ( (swwm_ps_resetitems == 2) && !wiped )
{
SWWMUtility.ResetItems(self);
resetitems = true;
}
if ( (swwm_ps_resethealth == 2) && !wiped )
{
SWWMUtility.ResetHealth(self);
resethealth = true;
}
}
if ( invwipe&WIPE_MAP )
{
if ( (swwm_ps_fullreset == 1) && !wiped )
{
SWWMUtility.WipeInventory(self,swwm_resetscore);
wiped = true;
}
if ( (swwm_ps_resetammo == 1) && !wiped && !resetammo )
SWWMUtility.ResetAmmo(self);
if ( (swwm_ps_resetitems == 1) && !wiped && !resetitems )
SWWMUtility.ResetItems(self);
if ( (swwm_ps_resethealth == 1) && !wiped && !resethealth )
SWWMUtility.ResetHealth(self);
}
}
invwipe = 0;
}
override void Travelled()
{
// reinitialize
dashfuel = default.dashfuel;
last_boost = 0;
last_kick = 0;
hasrevived = false;
blinktime = 30;
// cancel dash/boost
A_StopSound(CHAN_JETPACK);
fuelcooldown = 0.;
dashcooldown = 0.;
dashboost = 0.;
// prevent sudden stomping if we were previously falling
lastvelz = vel.z;
// early cancel gestures
if ( player )
{
if ( player.ReadyWeapon is 'SWWMItemGesture' )
player.ReadyWeapon = SWWMItemGesture(player.ReadyWeapon).gest;
if ( player.ReadyWeapon is 'SWWMGesture' )
{
player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon;
player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect"));
}
}
// re-add ourselves to the "suckable list" (otherwise the Ynykron Singularity won't hurt us)
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd && (hnd.SuckableActors.Find(self) >= hnd.SuckableActors.Size()) )
hnd.SuckableActors.Push(self);
// re-attach shadow
if ( !swwm_shadows ) return;
let ti = ThinkerIterator.Create("SWWMShadow");
Actor a;
while ( a = Actor(ti.Next()) )
{
if ( a.target == self )
return; // shadow already attached
}
SWWMShadow.Track(self);
ti.Destroy();
}
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;
}
// let's customize our gravity
override void FallAndSink( double grav, double oldfloorz )
{
if ( !player || (player.mo != self) || (player.cheats&CF_TOTALLYFROZEN) )
{
Super.FallAndSink(grav,oldfloorz);
return;
}
// do nothing if standing on ground or "floating"
if ( player.onground || bNOGRAVITY ) return;
// ensure we don't pass terminal velocity just from falling
if ( vel.z < -50 ) return;
// we don't care about "the doom way" here, gravity is
// ALWAYS in effect when not standing on solid ground
if ( waterlevel > 1 )
{
// sink faster
grav *= .35;
}
// reduce gravity while we're boosting
else if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Boost")) )
grav *= .25;
vel.z -= grav;
}
// the default PickWeapon code breaks when dual weapons are involved
// so we gotta roll out our own here
//
// I could rewrite PickNextWeapon and PickPrevWeapon as well, but nah
// those always call CheckAmmo so it's fine
private Weapon TraverseSlot( int slot, bool checkammo, int start, int end, Weapon cur )
{
for ( int i=start; i>=end; i-- )
{
let type = player.weapons.GetWeapon(slot,i);
let w = Weapon(player.mo.FindInventory(type));
if ( !w || (w == cur) || (cur && cur.bPOWERED_UP && (w == cur.SisterWeapon)) )
continue;
String sn = w.GetClassName();
bool dual = ((type is 'SWWMWeapon') && (sn.Left(4) ~== "Dual"));
if ( (!checkammo && (!dual || (w.SisterWeapon && (w.SisterWeapon.Amount > 1))))
|| w.CheckAmmo(Weapon.EitherFire,false) )
return w;
}
return null;
}
override Weapon PickWeapon( int slot, bool checkammo )
{
int slotsize = player.weapons.SlotSize(slot);
let cur = player.ReadyWeapon;
bool found = false;
int cs, ci;
if ( cur ) [found, cs, ci] = player.weapons.LocateWeapon(cur.GetClass());
if ( found && (cs == slot) )
{
// traverse the slot down from current index
let w = TraverseSlot(slot,checkammo,ci-1,0,cur);
if ( !w ) w = TraverseSlot(slot,checkammo,slotsize-1,ci+1,cur);
return w?w:cur;
}
let w = TraverseSlot(slot,checkammo,slotsize-1,0,cur);
return w?w:cur;
}
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 A_JumpIf(player&&(player.mo==self)&&(abs(player.cmd.yaw|player.cmd.pitch)>128),"Turn");
Wait;
See:
// normal walking
#### # 2;
XZW1 BCD 2 A_FastJump(null,"SeeRun","SeeFast");
XZW1 E 0 A_Footstep(1);
XZW1 EFGHIJKL 2 A_FastJump(null,"SeeRun","SeeFast");
XZW1 M 0 A_Footstep(-1);
XZW1 MNOPA 2 A_FastJump(null,"SeeRun","SeeFast");
Goto See+1;
Turn:
#### # 8 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZW1 C 1 A_JumpIf(!player||!(player.cmd.yaw|player.cmd.pitch),1);
Wait;
XZW1 C 3 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
Goto Spawn+1;
SeeRun:
#### # 2;
XZWI PQR 2 A_FastJump("See",null,"SeeFast");
XZWI S 0 A_Footstep(1,1);
XZWI STUVWX 2 A_FastJump("See",null,"SeeFast");
XZWI Y 0 A_Footstep(-1,1);
XZWI YZ 2 A_FastJump("See",null,"SeeFast");
XZW1 A 2 A_FastJump("See",null,"SeeFast");
Goto SeeRun+1;
SeeFast:
// sprinting
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
XZW1 QRS 2;
Goto SeeFastLoop;
SeeFastLoop:
// keep sprinting
XZW1 T 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 U 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 V 0 A_Footstep(1,2);
XZW1 V 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 W 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 X 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 Y 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 Z 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 A 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 B 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 C 0 A_Footstep(-1,2);
XZW2 C 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 D 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 E 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 F 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 G 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
Goto SeeFastLoop;
SeeFastEnd:
// brake
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
XZW2 HIJKL 2;
Goto Spawn+1;
Pain:
// ouchy
XZW1 A 1 A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchPain");
XZW2 M 1 A_DemoPain();
XZW2 NOPQ 1;
Goto Spawn+1;
Death:
XDeath:
// ded
XZW1 A 0 A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchDeath");
XZW1 A 2
{
A_DemoScream();
bSOLID = true; // we need this to avoid clipping into things
}
XZW2 RSTUVWXYZ 2;
XZW3 ABCDEFG 2;
XZW3 H 1 A_DMFade();
Wait;
Boost:
// start boost
#### # 2;
XZW3 IJKLMNO 2
{
if ( player.onground||bNoGravity )
return ResolveState("BoostEnd");
A_BoostUp(true);
return ResolveState(null);
}
// keep boost
XZW3 P 1
{
if ( player.onground||bNoGravity )
return ResolveState("BoostEnd");
A_BoostUp(false);
return ResolveState(null);
}
XZW3 P 1 A_JumpIf((vel.z<-10)&&(pos.z>(floorz+80)),"Fall");
Goto Boost+8;
BoostEnd:
// stop boost
#### # 2;
XZW3 PQRSTUVW 2;
Goto Spawn+1;
Jump:
#### # 2;
XZWJ ABCDEF 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
Goto FallLoop;
Fall:
// start fall
#### # 4;
XZW3 XYZ 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
XZW4 AB 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
Goto FallLoop;
FallLoop:
// falling
XZW4 CDEFGH 3 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
Goto FallLoop;
FallEnd:
// landing
XZW4 CIJKLMN 2;
Goto Spawn+1;
Dash:
#### # 2;
XZW4 O 2 A_Dash();
XZW4 PQRS 2 A_Dash();
Goto Dash+2;
DashEnd:
XZW4 TUVWX 2;
Goto Spawn+1;
Wave:
#### # 3;
XZW4 YZ 3;
XZW5 ABCDEFGHIJKLM 3;
Goto Spawn+1;
Approve:
#### # 3;
XZW5 NOPQR 3;
XZW5 S 3 { facegrin = true; }
XZW5 TUVWXYZ 3;
XZW6 ABCD 3;
Goto Spawn+1;
Victory:
#### # 3;
XZW6 EFGH 3;
XZW6 I 3 { facegrin = true; }
XZW6 JKLMNOPQRSTUVW 3;
Goto Spawn+1;
BlowKiss:
#### # 3;
XZWD EFGH 3;
XZWD I 3 { faceblink = true; }
XZWD JKLMNO 3;
XZWD P 3 { facewink = true; }
XZWD QRSTUVW 3;
Goto Spawn+1;
Headpat:
#### # 3;
XZWH ST 3;
XZWH U 2;
XZWH V 2 { facegrin = true; }
XZWH W 2;
XZWH XYZ 1;
XZWI A 1;
XZWI B 2;
XZWI C 2 { facegrin = true; }
XZWI DE 2;
XZWI FG 1;
HeadpatLoop:
XZWI H 1;
XZWH XYZ 1;
XZWI A 1;
XZWI B 2;
XZWI C 2 { facegrin = true; }
XZWI DE 2;
XZWI FGH 1;
XZWI IJK 2;
XZWI LMNO 3;
Goto Spawn+1;
Ragepat:
#### # 3;
XZWH ST 2;
XZWH UVW 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI FH 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI FH 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI F 1;
RagepatLoop:
XZWI H 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI FH 1;
XZWI IJK 1;
XZWI LMNO 2;
Goto Spawn+1;
Missile:
// attacking
#### # 2;
XZW6 XYZ 2;
XZW7 ABC 2;
Goto Spawn+1;
Melee:
// ponch
#### # 2;
XZW8 TUVWXYZ 2;
XZW9 ABCDEF 2;
Goto Spawn+1;
FastMelee:
// ponch (fast)
#### # 2;
XZW8 TUVWXYZ 1;
XZW9 ABCDE 1;
XZW9 F 2;
Goto Spawn+1;
Reload:
// reload
#### # 2;
XZW9 GHIJKLMNOPQRSTUVWXYZ 2;
XZWA ABCDE 2;
Goto Spawn+1;
FastReload:
// reload (fast)
#### # 2;
XZW9 GHIJKLMNOPQRSTUVWXYZ 1;
XZWA ABCDE 1;
Goto Spawn+1;
CheckGun:
// speen
#### # 2;
XZWA FGHIJKLMNOPQRSTUVWXY 2;
Goto Spawn+1;
StartCrouch:
// go crouching
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
XZW7 DEFGH 1;
XZW7 IJKL 2;
Goto Crouch+1;
Crouch:
#### # 4;
XZW7 M -1;
Stop;
CrouchMove:
#### # 4;
XZW7 MN 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZW7 OPQRS 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZW7 TUV 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
Goto CrouchMove+1;
CrouchMoveRun:
#### # 3;
XZW7 MN 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZW7 OPQRS 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZW7 TUV 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
Goto CrouchMoveRun+1;
CrouchMoveFast:
#### # 2;
XZW7 MN 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZW7 OPQRS 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZW7 TUV 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
Goto CrouchMoveFast+2;
CrouchWave:
#### # 3;
XZWF ABCDEFGHIJKLMNO 3;
Goto Crouch+1;
CrouchApprove:
#### # 3;
XZWF PQRST 3;
XZWF U 3 { facegrin = true; }
XZWF VWXYZ 3;
XZWG ABCDEF 3;
Goto Crouch+1;
CrouchVictory:
#### # 3;
XZWG GHIJ 3;
XZWG K 3 { facegrin = true; }
XZWG LMNOPQRSTUVWXY 3;
Goto Crouch+1;
CrouchBlowKiss:
#### # 3;
XZWG Z 3;
XZWH ABC 3;
XZWH D 3 { faceblink = true; }
XZWH EFGHIJ 3;
XZWH K 3 { facewink = true; }
XZWH LMNOPQR 3;
Goto Crouch+1;
CrouchMissile:
XZW7 M 2;
XZW7 WXYZ 2;
XZW8 AB 2;
Goto Crouch+1;
CrouchMelee:
XZW7 M 2;
XZWA Z 2;
XZWB ABCDEFGHIJKL 2;
Goto Crouch+1;
CrouchFastMelee:
XZW7 M 2;
XZWA Z 1;
XZWB ABCDEFGHIJK 1;
XZWB L 2;
Goto Crouch+1;
CrouchReload:
XZW7 M 2;
XZWB MNOPQRSTUVWXYZ 2;
XZWC ABCDEFGHIJ 2;
Goto Crouch+1;
CrouchFastReload:
XZW7 M 2;
XZWB MNOPQRSTUVWXYZ 1;
XZWC ABCDEFGHIJ 1;
Goto Crouch+1;
CrouchCheckGun:
XZW7 M 2;
XZWC LMNOPQRSTUVWXYZ 2;
XZWD ABCD 2;
Goto Crouch+1;
CrouchPain:
XZW7 M 1;
XZW8 C 1 A_DemoPain();
XZW8 DEF 1;
Goto Crouch+1;
CrouchDeath:
XZW7 M 2
{
A_DemoScream();
bSOLID = true; // we need this to avoid clipping into things
}
XZW8 GHIJK 2;
XZW8 L 1 A_DMFade();
Wait;
EndCrouch:
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
XZW8 MNOPQRS 2;
Goto Spawn+1;
Float:
#### # 3;
XZWD XYZ 3;
XZWE ABCDEFGHI 3;
Goto Float+1;
Swim:
#### # 2;
XZWE JKL 2;
Goto SwimLoop;
SwimLoop:
#### # 5;
XZWE MNO 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZWE PQRSTU 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZWE VWX 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
Goto SwimLoop+1;
SwimLoopRun:
#### # 3;
XZWE MNO 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZWE PQRSTU 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZWE VWX 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
Goto SwimLoopRun+1;
SwimLoopFast:
#### # 2;
XZWE MNO 2 A_FastJump("SwimLoop","SwimLoopRun",null);
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZWE PQRSTU 2 A_FastJump("SwimLoop","SwimLoopRun",null);
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZWE VWX 2 A_FastJump("SwimLoop","SwimLoopRun",null);
Goto SwimLoopFast+1;
SwimEnd:
#### # 2;
XZWE MYZ 2;
Goto Float+1;
}
}
Class SWWMVoodooDoll : PlayerPawn
{
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
// as simple as it gets
return "$OB_VOODOO";
}
Default
{
Tag "$FN_VOODOO";
Speed 1;
Health 100;
Radius 16;
Height 56;
Mass 100;
PainChance 255;
+NOBLOOD;
+DONTGIB;
+NOICEDEATH;
+DONTCORPSE;
+NOSKIN;
-FRIENDLY;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Pain:
XZW1 A 1;
XZW1 B 2 A_StartSound("voodoodoll/hit",CHAN_BODY,CHANF_OVERLAP);
XZW1 CDEF 2;
Goto Spawn;
Death:
XZW1 A 2
{
A_StartSound("voodoodoll/fall",CHAN_BODY,CHANF_OVERLAP);
A_NoBlocking();
}
XZW1 GHIJKLMNO 2;
XZW1 PQR 2;
XZW1 S -1;
Stop;
}
}
// for the doom 2 cast
Class CastDemolitionist : Actor
{
Default
{
DeathSound "demolitionist/death";
}
States
{
Spawn:
See:
ZYX1 ABCDEFGHIJKLMNOP 2;
Loop;
Missile:
ZYX1 A 2;
ZYX2 ABCDEF 2;
Goto See;
Death:
ZYX1 A 2;
ZYX3 ABCDEFGHIJKLMNOP 2;
ZYX3 Q -1;
Stop;
}
}