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