4020 lines
128 KiB
Text
4020 lines
128 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, lastbust;
|
|
|
|
transient bool ingivecheat;
|
|
|
|
Property DashFuel : dashfuel;
|
|
|
|
EDemoFaceState facestate;
|
|
int paindir;
|
|
int facetimer;
|
|
int blinktime;
|
|
transient int oldfaceidx;
|
|
transient int rss;
|
|
transient bool facedamage, facegrin, facesad, facewink, faceblink;
|
|
|
|
transient CVar tagcolor;
|
|
int oldtagcolor;
|
|
|
|
transient int magtime;
|
|
SWWMMagItem magitem;
|
|
int magitem_cnt;
|
|
|
|
SWWMShadow myshadow;
|
|
|
|
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 && !i.CheckSight(self,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
|
|
SWWMItemSense.Spawn(self,i);
|
|
}
|
|
bt.Destroy();
|
|
}
|
|
SWWMItemsense itm = itemsense;
|
|
SWWMItemsense prev = null, next;
|
|
while ( itm )
|
|
{
|
|
next = itm.next;
|
|
if ( itm.Tick() )
|
|
{
|
|
if ( prev ) prev.next = next;
|
|
else itemsense = next;
|
|
itm.Destroy();
|
|
}
|
|
else prev = itm;
|
|
itm = next;
|
|
}
|
|
}
|
|
void 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);
|
|
}
|
|
}
|
|
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) || (freezetics > 0) ) return;
|
|
UpdateFace();
|
|
UpdateTags();
|
|
if ( hasteleported )
|
|
{
|
|
// we just got teleported, don't count the travel distance
|
|
oldpos = pos;
|
|
hasteleported = false;
|
|
}
|
|
if ( !selflight )
|
|
{
|
|
selflight = new("DemolitionistSelfLight");
|
|
selflight.ChangeStatNum(STAT_USER);
|
|
selflight.target = self;
|
|
selflight.Tick();
|
|
}
|
|
if ( !myshadow ) myshadow = SWWMShadow.Track(self);
|
|
// double-check that we have these
|
|
if ( !FindInventory("AlmasteelPlating") )
|
|
{
|
|
let ap = Inventory(Spawn("AlmasteelPlating"));
|
|
if ( !ap.CallTryPickup(self) ) ap.Destroy();
|
|
}
|
|
if ( !FindInventory("SayaCollar") )
|
|
{
|
|
let sc = Inventory(Spawn("SayaCollar"));
|
|
if ( !sc.CallTryPickup(self) ) sc.Destroy();
|
|
}
|
|
// this is why we need mod cvar callbacks
|
|
if ( swwm_singlefirst != oldsinglefirst )
|
|
WeaponSlots.SetupWeaponSlots(self);
|
|
oldsinglefirst = swwm_singlefirst;
|
|
// overheal fading
|
|
if ( !isFrozen() && !(player.cheats&CF_TOTALLYFROZEN) )
|
|
{
|
|
if ( (health <= 200) || (health > oldhealth) )
|
|
{
|
|
healtimer = 0;
|
|
healcooldown = 80;
|
|
}
|
|
else if ( health > 200 )
|
|
{
|
|
if ( healcooldown > 0 ) healcooldown--;
|
|
else
|
|
{
|
|
if ( health > 1000 )
|
|
{
|
|
let spr = DivineSpriteEffect(FindInventory("DivineSpriteEffect"));
|
|
if ( !spr || spr.bHealDone )
|
|
A_SetHealth(max(1000,health-10));
|
|
if ( health <= 1000 ) healcooldown = 40;
|
|
}
|
|
else if ( health > 500 )
|
|
{
|
|
if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%3) )
|
|
A_SetHealth(health-1);
|
|
if ( health <= 500 ) healcooldown = 20;
|
|
}
|
|
else if ( health > 200 )
|
|
{
|
|
if ( !FindInventory("RefresherRegen") && !(healtimer%12) )
|
|
A_SetHealth(health-1);
|
|
}
|
|
healtimer++;
|
|
}
|
|
}
|
|
}
|
|
oldhealth = health;
|
|
oldlagvel = lagvel;
|
|
oldlagready = lagready;
|
|
if ( player.weaponstate&WF_WEAPONBOBBING ) lagready = lagready*.9+.1;
|
|
else lagready = lagready*.4;
|
|
lagvel = lagvel*.8+vel*.2;
|
|
double traveldist = level.Vec3Diff(oldpos,pos).length();
|
|
if ( !player.onground || bNoGravity )
|
|
{
|
|
if ( waterlevel > 1 )
|
|
{
|
|
cairtime = 0;
|
|
mystats.swimdist += traveldist;
|
|
}
|
|
else
|
|
{
|
|
cairtime++;
|
|
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
|
|
mystats.airdist += traveldist;
|
|
}
|
|
if ( (vel.z < -fallingscreamminspeed) && (vel.z > -fallingscreammaxspeed) && (player == players[consoleplayer]) )
|
|
SWWMHandler.AddOneliner("falling",2,30);
|
|
}
|
|
else
|
|
{
|
|
SWWMHandler.CancelOneliner("falling");
|
|
airscreamtime = 0;
|
|
cairtime = 0;
|
|
mystats.grounddist += traveldist;
|
|
SWWMUtility.AchievementProgressIncDouble("travel",traveldist/32000.,player);
|
|
}
|
|
// spawn bubbles while underwater
|
|
if ( (waterlevel > 1) && !Random[ExploS](0,5) )
|
|
{
|
|
int numpt = Random[ExploS](-2,2);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let p = Spawn("SWWMBubble",Vec3Offset(FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](height*.1,height*.9)));
|
|
p.scale *= FRandom[ExploS](.02,.2);
|
|
p.vel += vel*.2;
|
|
}
|
|
}
|
|
CheckUnderwaterAmb();
|
|
SenseItems();
|
|
CheckItemMagnet();
|
|
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
|
|
if ( vel.length() > ((3600*GameTicRate)/32000.) )
|
|
SWWMUtility.AchievementProgress("sanic",int((vel.length()*3600*GameTicRate)/32000.),player);
|
|
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
|
if ( player.onground && !bNoGravity && !lastground )
|
|
{
|
|
// bump down weapon
|
|
bumpvelz.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;
|
|
Vector2 accel = RotateVector(nmove,angle);
|
|
accel *= fs/320.;
|
|
double spd = vel.xy.length();
|
|
double maxspd = fs*12.;
|
|
if ( spd > maxspd ) vel.xy = (vel.xy+accel/GameTicRate).unit()*spd;
|
|
else vel.xy = vel.xy+accel/GameTicRate;
|
|
}
|
|
if ( !(player.cheats&CF_PREDICTING) && (nmove.length() > 0.) )
|
|
PlayRunning();
|
|
if ( player.cheats&CF_REVERTPLEASE )
|
|
{
|
|
player.cheats &= ~CF_REVERTPLEASE;
|
|
player.camera = player.mo;
|
|
}
|
|
}
|
|
else if ( player.onground && ShouldDecelerate(floorsector) )
|
|
{
|
|
// quickly decelerate if we're not holding movement keys
|
|
// (account for slippery floors, and assume approx .9 friction as "midpoint" for 85% reduction)
|
|
double fact = clamp(floorsector.GetFriction(0)*.95,.5,1.);
|
|
vel *= fact;
|
|
player.vel *= fact;
|
|
}
|
|
if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE);
|
|
}
|
|
guideangle *= .9;
|
|
guidepitch *= .9;
|
|
guideroll *= .9;
|
|
// anchor to ground when going down steps
|
|
if ( lastground && !player.onground && !bFly && !bFlyCheat && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z <= 0) && !isdashing )
|
|
{
|
|
// test for gap crossing (i.e: climbing up platforms with holes between them)
|
|
Vector3 storepos = pos;
|
|
double storefloorz = floorz;
|
|
bool crossgap = false;
|
|
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
|
|
{
|
|
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),true);
|
|
if ( floorz < storepos.z ) continue;
|
|
crossgap = true;
|
|
break;
|
|
}
|
|
SetOrigin(storepos,true);
|
|
floorz = storefloorz;
|
|
if ( !crossgap )
|
|
{
|
|
ssup = max(0,(pos.z-floorz));
|
|
SetZ(floorz);
|
|
lastground = player.onground = true;
|
|
}
|
|
}
|
|
if ( player.onground ) lastgroundtic = level.maptime;
|
|
else lastairtic = level.maptime;
|
|
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
|
|
PlayIdle();
|
|
Vector3 dodge = (0,0,0), x, y, z;
|
|
[x, y, z] = 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;
|
|
break;
|
|
}
|
|
Sector s = d.HitLine.sidedef[!d.LineSide].sector;
|
|
double backfloorz = s.floorplane.ZAtPoint(d.HitLocation.xy);
|
|
double backceilz = s.ceilingplane.ZAtPoint(d.HitLocation.xy);
|
|
// cling to 3D floor if hit
|
|
if ( d.Hit3DFloor ) backfloorz = d.Hit3DFloor.top.ZAtPoint(d.HitLocation.xy);
|
|
if ( ((backceilz-backfloorz) < Height) || ((pos.z+Height+32) < backfloorz) ) // nope
|
|
{
|
|
wallclimb = false;
|
|
break;
|
|
}
|
|
climbvelz = 1.5*sqrt(max(1.,backfloorz-pos.z));
|
|
}
|
|
else if ( d.HitType == TRACE_HitActor )
|
|
{
|
|
if ( ((d.HitActor.ceilingz-d.HitActor.pos.z+d.HitActor.Height) < Height) || ((pos.z+Height+32) < (d.HitActor.pos.z+d.HitActor.Height)) ) // nope
|
|
{
|
|
wallclimb = false;
|
|
break;
|
|
}
|
|
climbvelz = 1.5*sqrt(max(1.,(d.HitActor.pos.z+d.HitActor.Height)-pos.z));
|
|
}
|
|
jumpactor = d.HitActor;
|
|
walldir = (walldir*.7+AngleToVector(ang)*.3);
|
|
break;
|
|
}
|
|
}
|
|
if ( wallclimb ) break;
|
|
}
|
|
if ( wallclimb ) break;
|
|
}
|
|
if ( wallclimb ) break;
|
|
}
|
|
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
|
|
if ( level.maptime < (lastgroundtic+4) )
|
|
{
|
|
walljump = false;
|
|
wallclimb = false;
|
|
jumpactor = null;
|
|
}
|
|
if ( player.crouchoffset ) player.crouching = 1;
|
|
else if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return;
|
|
else if ( bNoGravity ) vel.z = 3;
|
|
else if ( level.IsJumpingAllowed()
|
|
&& ((player.onground && (player.jumptics == 0))
|
|
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) )
|
|
{
|
|
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
|
|
return;
|
|
double jumpvelz = JumpZ*35./GameTicRate;
|
|
double jumpfac = 0;
|
|
for ( let p=Inv; p; p=p.Inv )
|
|
{
|
|
let pp = PowerHighJump(p);
|
|
if ( pp )
|
|
{
|
|
double f = pp.Strength;
|
|
if ( f > jumpfac ) jumpfac = f;
|
|
}
|
|
}
|
|
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
|
|
bool raging = FindInventory("RagekitPower");
|
|
if ( raging ) jumpvelz *= 2.;
|
|
double pvelz = vel.z;
|
|
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
|
|
{
|
|
// check for wall stuff
|
|
if ( walljump )
|
|
{
|
|
if ( vel.z < 10. )
|
|
vel.z += 2.*jumpvelz+clamp(-pvelz*.6,0.,30.);
|
|
vel.xy += walldir*20*Speed;
|
|
lastbump *= .95;
|
|
}
|
|
else if ( wallclimb )
|
|
{
|
|
if ( vel.z < 10. )
|
|
vel.z += climbvelz+clamp(-pvelz*.95,0.,30.);
|
|
vel.xy += walldir*10*Speed;
|
|
lastbump *= .97;
|
|
}
|
|
if ( walljump && jumpactor && jumpactor.bSHOOTABLE )
|
|
{
|
|
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
|
|
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",pos);
|
|
ps.damagetype = 'Jump';
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
}
|
|
if ( walljump || wallclimb )
|
|
{
|
|
last_kick = level.maptime+1;
|
|
SWWMUtility.AchievementProgressInc("jump",1,player);
|
|
}
|
|
}
|
|
bOnMobj = false;
|
|
player.jumpTics = -1;
|
|
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
|
|
{
|
|
dashboost = 3.;
|
|
boostcooldown = 20;
|
|
if ( vel.z < 10. )
|
|
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
|
|
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
|
|
lastbump *= .95;
|
|
mystats.boostcount++;
|
|
last_boost = level.maptime+1;
|
|
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 double GetDeathHeight()
|
|
{
|
|
double basedeathheight = Super.GetDeathHeight();
|
|
// limit death height to crouch height, to avoid dying players from getting stuck on revive
|
|
return max(Height*.3,basedeathheight);
|
|
}
|
|
override void 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();
|
|
if ( myshadow ) myshadow.Destroy();
|
|
// clean up our objects
|
|
SWWMItemSense si = itemsense;
|
|
while ( si )
|
|
{
|
|
let next = si.next;
|
|
si.Destroy();
|
|
si = next;
|
|
}
|
|
SWWMMagItem mi = magitem;
|
|
while ( mi )
|
|
{
|
|
let next = mi.next;
|
|
mi.Destroy();
|
|
mi = next;
|
|
}
|
|
magitem_cnt = 0;
|
|
// disable death exits
|
|
if ( player && (player.playerstate == PST_DEAD) )
|
|
{
|
|
player.cheats &= ~(CF_FROZEN|CF_TOTALLYFROZEN);
|
|
player.Resurrect();
|
|
player.damagecount = 0;
|
|
player.bonuscount = 0;
|
|
player.poisoncount = 0;
|
|
roll = 0;
|
|
if ( special1 > 2 ) special1 = 0;
|
|
}
|
|
// inventory wipes
|
|
if ( invwipe && (player.playerstate != PST_DEAD) )
|
|
{
|
|
bool wiped = false;
|
|
bool resetammo = false;
|
|
bool resetitems = false;
|
|
bool resethealth = false;
|
|
if ( invwipe&WIPE_EPISODE )
|
|
{
|
|
SWWMUtility.WipeInventory(self,swwm_resetscore);
|
|
wiped = true;
|
|
}
|
|
if ( invwipe&WIPE_CLUSTER )
|
|
{
|
|
if ( (swwm_ps_fullreset == 2) && !wiped )
|
|
{
|
|
SWWMUtility.WipeInventory(self,swwm_resetscore);
|
|
wiped = true;
|
|
}
|
|
if ( (swwm_ps_resetammo == 2) && !wiped )
|
|
{
|
|
SWWMUtility.ResetAmmo(self);
|
|
resetammo = true;
|
|
}
|
|
if ( (swwm_ps_resetitems == 2) && !wiped )
|
|
{
|
|
SWWMUtility.ResetItems(self);
|
|
resetitems = true;
|
|
}
|
|
if ( (swwm_ps_resethealth == 2) && !wiped )
|
|
{
|
|
SWWMUtility.ResetHealth(self);
|
|
resethealth = true;
|
|
}
|
|
}
|
|
if ( invwipe&WIPE_MAP )
|
|
{
|
|
if ( (swwm_ps_fullreset == 1) && !wiped )
|
|
{
|
|
SWWMUtility.WipeInventory(self,swwm_resetscore);
|
|
wiped = true;
|
|
}
|
|
if ( (swwm_ps_resetammo == 1) && !wiped && !resetammo )
|
|
SWWMUtility.ResetAmmo(self);
|
|
if ( (swwm_ps_resetitems == 1) && !wiped && !resetitems )
|
|
SWWMUtility.ResetItems(self);
|
|
if ( (swwm_ps_resethealth == 1) && !wiped && !resethealth )
|
|
SWWMUtility.ResetHealth(self);
|
|
}
|
|
}
|
|
invwipe = 0;
|
|
}
|
|
override void Travelled()
|
|
{
|
|
// reinitialize
|
|
dashfuel = default.dashfuel;
|
|
last_boost = 0;
|
|
last_kick = 0;
|
|
hasrevived = false;
|
|
blinktime = 30;
|
|
// cancel dash/boost
|
|
A_StopSound(CHAN_JETPACK);
|
|
fuelcooldown = 0.;
|
|
dashcooldown = 0.;
|
|
dashboost = 0.;
|
|
// prevent sudden stomping if we were previously falling
|
|
lastvelz = vel.z;
|
|
// early cancel gestures
|
|
if ( player )
|
|
{
|
|
if ( player.ReadyWeapon is 'SWWMItemGesture' )
|
|
player.ReadyWeapon = SWWMItemGesture(player.ReadyWeapon).gest;
|
|
if ( player.ReadyWeapon is 'SWWMGesture' )
|
|
{
|
|
player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon;
|
|
player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect"));
|
|
}
|
|
}
|
|
// re-add ourselves to the "suckable list" (otherwise the Ynykron Singularity won't hurt us)
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd && (hnd.SuckableActors.Find(self) >= hnd.SuckableActors.Size()) )
|
|
hnd.SuckableActors.Push(self);
|
|
}
|
|
override bool PreTeleport( Vector3 destpos, double destangle, int flags )
|
|
{
|
|
// store old pos
|
|
pretelepos = pos;
|
|
return true;
|
|
}
|
|
override void PostTeleport( Vector3 destpos, double destangle, int flags )
|
|
{
|
|
hasteleported = true; // notify tick that we teleported, so it ignores the travel distance
|
|
mystats.teledist += level.Vec3Diff(pretelepos,pos).length();
|
|
// reset all smooth bob variables if angles/velocity aren't carried over
|
|
if ( !(flags&TELF_KEEPORIENTATION) )
|
|
{
|
|
oldlagangle = lagangle = oldangle = angle;
|
|
oldlagpitch = lagpitch = oldpitch = pitch;
|
|
}
|
|
if ( !(flags&TELF_KEEPVELOCITY) )
|
|
{
|
|
oldlagvel = lagvel = vel;
|
|
lastvelz = vel.z;
|
|
}
|
|
// notify carried lamp that we just moved
|
|
let l = SWWMLamp(FindInventory("SWWMLamp"));
|
|
if ( l && l.thelamp )
|
|
CompanionLamp(l.thelamp).justteleport = true;
|
|
}
|
|
// 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;
|
|
}
|
|
}
|