3613 lines
106 KiB
Text
3613 lines
106 KiB
Text
// The Demolitionist
|
|
Class Demolitionist : PlayerPawn
|
|
{
|
|
int last_jump_held, last_boost, last_kick;
|
|
Vector3 dashdir;
|
|
double dashfuel, dashboost;
|
|
int dashcooldown, boostcooldown, fuelcooldown;
|
|
bool dashsnd;
|
|
bool sendtoground;
|
|
bool key_reentrant;
|
|
|
|
int lastdamage;
|
|
transient int lastdamagetic;
|
|
bool lastground;
|
|
int lastgroundtic;
|
|
double lastvelz, prevvelz;
|
|
double ssup;
|
|
|
|
transient CVar myvoice, mute, fly6dof, clrun;
|
|
|
|
SWWMStats mystats;
|
|
int cairtime;
|
|
|
|
int lastmpain;
|
|
|
|
double guideangle, guidepitch, guideroll;
|
|
|
|
// for weapon bobbing stuff
|
|
bool bumpdown;
|
|
double bumpvelz;
|
|
double oldangle, oldpitch;
|
|
double oldlagangle, oldlagpitch, oldlagready;
|
|
Vector3 oldlagvel;
|
|
double lagangle, lagpitch, lagready;
|
|
Vector3 lagvel;
|
|
|
|
enum EUnderType
|
|
{
|
|
UNDER_NONE,
|
|
UNDER_WATER,
|
|
UNDER_SLIME,
|
|
UNDER_LAVA
|
|
};
|
|
|
|
int lastunder;
|
|
Color undercol;
|
|
int deadtimer;
|
|
transient int revivefail;
|
|
|
|
transient int bumptic;
|
|
|
|
Actor selflight;
|
|
Actor oldencroached;
|
|
Vector3 oldencroachedpos;
|
|
|
|
Vector3 pretelepos;
|
|
|
|
SWWMItemSense itemsense;
|
|
int itemsense_cnt;
|
|
|
|
int healcooldown, healtimer, oldhealth;
|
|
bool scriptedinvul;
|
|
|
|
Property DashFuel : dashfuel;
|
|
|
|
Default
|
|
{
|
|
Speed 1;
|
|
Radius 16; // should be 9 in theory, but it'd be too thin
|
|
Height 56;
|
|
Mass 500;
|
|
PainChance 255;
|
|
Player.DisplayName "Demolitionist";
|
|
Player.StartItem "ExplodiumGun";
|
|
Player.StartItem "DeepImpact";
|
|
Player.ViewHeight 52;
|
|
Player.AirCapacity 0;
|
|
Player.GruntSpeed 20;
|
|
Player.SoundClass "demolitionist";
|
|
DamageFactor "Drowning", 0.;
|
|
DamageFactor "Poison", 0.;
|
|
DamageFactor "PoisonCloud", 0.;
|
|
DamageFactor "Falling", 0.;
|
|
Demolitionist.DashFuel 240.;
|
|
+NOBLOOD;
|
|
+DONTGIB;
|
|
+NOICEDEATH;
|
|
+DONTMORPH;
|
|
+DONTDRAIN;
|
|
}
|
|
// oh yay, more cheat modification
|
|
override void CheatGive( String name, int amount )
|
|
{
|
|
if ( !player.mo || (player.health <= 0) ) return;
|
|
int giveall = ALL_NO;
|
|
if ( name ~== "all" ) giveall = ALL_YES;
|
|
else if ( name ~== "everything" ) giveall = ALL_YESYES;
|
|
if ( giveall || (name ~== "health") )
|
|
{
|
|
if ( amount > 0 )
|
|
{
|
|
health = min(health+amount,1000);
|
|
player.health = health;
|
|
}
|
|
else player.health = health = 200;
|
|
}
|
|
if ( giveall || (name ~== "backpack") )
|
|
{
|
|
// Select the correct type of backpack based on the game
|
|
let type = (class<Inventory>)(gameinfo.backpacktype);
|
|
let def = GetDefaultByType(type);
|
|
if ( type ) GiveInventory(type,def.MaxAmount,true);
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( giveall || (name ~== "ammo") )
|
|
{
|
|
// Find every unique type of ammo. Give it to the player if
|
|
// he doesn't have it already, and set each to its maximum.
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (class<Ammo>)(AllActorClasses[i]);
|
|
if ( !type || (type.GetParentClass() != "Ammo") )
|
|
continue;
|
|
// Only give if it's for a valid weapon, unless using "give everything"
|
|
bool isvalid = false;
|
|
for ( int j=0; j<AllActorClasses.Size(); j++ )
|
|
{
|
|
let type2 = (class<Weapon>)(AllActorClasses[j]);
|
|
if ( !type2 ) continue;
|
|
let rep = GetReplacement(type2);
|
|
if ( (rep != type2) && !(rep is "DehackedPickup") ) continue;
|
|
readonly<Weapon> weap = GetDefaultByType(type2);
|
|
if ( !player.weapons.LocateWeapon(type2) || (weap.bCheatNotWeapon && (giveall != ALL_YESYES)) ) continue;
|
|
if ( (type2 is 'SWWMWeapon') && SWWMWeapon(weap).UsesAmmo(type) )
|
|
{
|
|
isvalid = true;
|
|
break;
|
|
}
|
|
if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) )
|
|
{
|
|
isvalid = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( !isvalid ) continue;
|
|
let ammoitem = FindInventory(type);
|
|
if ( !ammoitem )
|
|
{
|
|
ammoitem = Inventory(Spawn(type));
|
|
ammoitem.AttachToOwner(self);
|
|
ammoitem.Amount = ammoitem.MaxAmount;
|
|
}
|
|
else if ( ammoitem.Amount < ammoitem.MaxAmount )
|
|
ammoitem.Amount = ammoitem.MaxAmount;
|
|
}
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( giveall || (name ~== "armor") )
|
|
{
|
|
// Give only subclasses of SWWMArmor
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (Class<SWWMArmor>)(AllActorClasses[i]);
|
|
if ( !type || (type == 'SWWMArmor') ) continue;
|
|
if ( GetReplacement(type) == type )
|
|
{
|
|
let owned = FindInventory(type);
|
|
if ( owned )
|
|
{
|
|
owned.Amount = owned.MaxAmount;
|
|
continue;
|
|
}
|
|
let item = Inventory(Spawn(type));
|
|
item.ClearCounters(); // don't increase item counts
|
|
item.Amount = item.MaxAmount;
|
|
if ( !item.CallTryPickup(self) ) item.Destroy();
|
|
}
|
|
}
|
|
// Also give spares
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (Class<SWWMSpareArmor>)(AllActorClasses[i]);
|
|
if ( !type || (type == 'SWWMSpareArmor') ) continue;
|
|
if ( GetReplacement(type) == type )
|
|
{
|
|
let owned = FindInventory(type);
|
|
if ( owned )
|
|
{
|
|
owned.Amount = owned.MaxAmount;
|
|
continue;
|
|
}
|
|
let item = Inventory(Spawn(type));
|
|
item.ClearCounters(); // don't increase item counts
|
|
item.Amount = item.MaxAmount;
|
|
if ( !item.CallTryPickup(self) ) item.Destroy();
|
|
}
|
|
}
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( giveall || (name ~== "keys") )
|
|
{
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
if ( !(AllActorClasses[i] is "Key") ) continue;
|
|
// clean up redundant keys
|
|
if ( !(gameinfo.gametype&GAME_Chex) && ((AllActorClasses[i] is 'ChexRedCard') || (AllActorClasses[i] is 'ChexBlueCard') || (AllActorClasses[i] is 'ChexYellowCard')) ) continue;
|
|
else if ( (gameinfo.gametype&GAME_Chex) && !(AllActorClasses[i] is 'ChexRedCard') && !(AllActorClasses[i] is 'ChexBlueCard') && !(AllActorClasses[i] is 'ChexYellowCard') ) continue;
|
|
// don't give heretic keys in doom
|
|
if ( (gameinfo.gametype&GAME_DoomChex) && ((AllActorClasses[i] is 'KeyYellow') || (AllActorClasses[i] is 'KeyGreen') || (AllActorClasses[i] is 'KeyBlue')) ) continue;
|
|
let keyitem = GetDefaultByType(AllActorClasses[i]);
|
|
if ( keyitem.special1 )
|
|
{
|
|
let item = Inventory(Spawn(AllActorClasses[i]));
|
|
SWWMHandler.KeyTagFix(item);
|
|
if ( item is 'SWWMKey' ) SWWMKey(item).propagated = true; // no anim
|
|
if ( !item.CallTryPickup(self) ) item.Destroy();
|
|
}
|
|
}
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( giveall || (name ~== "weapons") )
|
|
{
|
|
let savedpending = player.PendingWeapon;
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (class<Weapon>)(AllActorClasses[i]);
|
|
if ( !type || (type == "Weapon") ) continue;
|
|
// no Ynykron in Strife
|
|
if ( (gameinfo.gametype&GAME_Strife) && (type is 'Ynykron') ) 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 ( (giveall == ALL_YESYES) || !def.bCheatNotWeapon )
|
|
GiveInventory(type,1,true);
|
|
}
|
|
}
|
|
player.PendingWeapon = savedpending;
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( giveall || (name ~== "artifacts") )
|
|
{
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (class<Inventory>)(AllActorClasses[i]);
|
|
if ( !type ) continue;
|
|
if ( !(gameinfo.gametype&(GAME_Hexen|GAME_Strife)) && (type is 'AmmoFabricator') ) continue; // no fabricators before hexen
|
|
// Don't give maxed items
|
|
let owned = FindInventory(type);
|
|
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
|
|
let def = GetDefaultByType (type);
|
|
if ( def.Icon.isValid() &&
|
|
!(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") && !(type is "Armor") && !(type is "Key") )
|
|
{
|
|
// Do not give replaced items unless using "give everything"
|
|
if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
|
|
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
|
|
}
|
|
}
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( giveall || (name ~== "puzzlepieces") )
|
|
{
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (class<PuzzleItem>)(AllActorClasses[i]);
|
|
if ( !type ) continue;
|
|
let def = GetDefaultByType(type);
|
|
if ( !def.Icon.isValid() ) continue;
|
|
// Do not give replaced items unless using "give everything"
|
|
if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
|
|
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
|
|
}
|
|
if ( !giveall ) return;
|
|
}
|
|
if ( (giveall == ALL_YESYES) || (name ~== "collectibles") )
|
|
{
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let type = (class<SWWMCollectible>)(AllActorClasses[i]);
|
|
if ( !type || (type == 'SWWMCollectible') ) continue;
|
|
let def = GetDefaultByType(type);
|
|
// check that we can collect it in this IWAD
|
|
if ( !(gameinfo.gametype&def.avail) ) continue;
|
|
let item = Inventory(Spawn(AllActorClasses[i]));
|
|
SWWMCollectible(item).propagated = true; // no score or anims
|
|
if ( !item.CallTryPickup(self) ) item.Destroy();
|
|
}
|
|
}
|
|
if ( giveall ) return;
|
|
let type = name;
|
|
if ( !type )
|
|
{
|
|
if ( PlayerNumber() == consoleplayer )
|
|
A_Log(String.Format("Unknown item \"%s\"\n",name));
|
|
}
|
|
else GiveInventory(type,amount,true);
|
|
}
|
|
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( mod == 'Jump' ) return StringTable.Localize("$O_JUMP");
|
|
if ( mod == 'Dash' ) return StringTable.Localize("$O_DASH");
|
|
if ( mod == 'Buttslam' ) return StringTable.Localize("$O_BUTT");
|
|
if ( mod == 'GroundPound' ) return StringTable.Localize("$O_POUND");
|
|
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
|
}
|
|
override void GiveDefaultInventory()
|
|
{
|
|
Super.GiveDefaultInventory();
|
|
// preloaded gun
|
|
let eg = ExplodiumGun(FindInventory("ExplodiumGun"));
|
|
if ( !eg ) return;
|
|
eg.clipcount = eg.default.clipcount;
|
|
eg.chambered = true;
|
|
let eg2 = DualExplodiumGun(eg.SisterWeapon);
|
|
if ( !eg2 ) return;
|
|
eg2.clipcount = eg2.default.clipcount;
|
|
eg2.chambered = true;
|
|
}
|
|
override void ClearInventory()
|
|
{
|
|
Super.ClearInventory();
|
|
// remove some specific "undroppable" items
|
|
Actor last = self;
|
|
while ( last.inv )
|
|
{
|
|
let i = last.inv;
|
|
if ( (i is 'SWWMArmor') || (i is 'HammerspaceEmbiggener') )
|
|
{
|
|
i.DepleteOrDestroy();
|
|
if ( !i.bDestroyed ) last = i;
|
|
}
|
|
else last = i;
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
mystats = SWWMStats.Find(player);
|
|
lastground = true;
|
|
}
|
|
void A_Dash()
|
|
{
|
|
vel += dashdir*dashboost;
|
|
player.vel = vel.xy;
|
|
if ( dashboost < .2 ) dashboost = 0.;
|
|
else
|
|
{
|
|
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:800);
|
|
dashboost *= (player.cmd.buttons&BT_USER2)?.9:.1;
|
|
}
|
|
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
|
|
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
|
|
dashcooldown = min(40,max(10,int(dashcooldown*1.4)));
|
|
fuelcooldown = max(30,fuelcooldown);
|
|
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
|
|
SetStateLabel("DashEnd");
|
|
}
|
|
void A_BoostUp( bool initial = false )
|
|
{
|
|
vel += (0,0,1)*dashboost;
|
|
player.vel = vel.xy;
|
|
if ( dashboost < .2 ) dashboost = 0.;
|
|
else
|
|
{
|
|
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:800);
|
|
dashboost *= (player.cmd.buttons&BT_JUMP)?.95:.4;
|
|
last_boost = level.maptime+1;
|
|
}
|
|
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
|
|
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
|
|
if ( ((dashfuel <= 0.) || (dashboost <= 0.)) )
|
|
{
|
|
if ( !initial )
|
|
{
|
|
if ( player.onground ) SetStateLabel("JumpEnd");
|
|
else SetStateLabel("Fall");
|
|
}
|
|
return;
|
|
}
|
|
fuelcooldown = max(20,fuelcooldown);
|
|
}
|
|
void SenseItems()
|
|
{
|
|
let thesight = FindInventory("Omnisight");
|
|
let bt = BlockThingsIterator.Create(self,800);
|
|
while ( bt.Next() )
|
|
{
|
|
let i = Inventory(bt.Thing);
|
|
if ( !i || i.bINVISIBLE || !i.bSPECIAL || i.Owner || !SWWMUtility.SphereIntersect(i,pos,800) ) continue;
|
|
if ( !thesight && !CheckSight(i,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
|
|
SWWMItemSense.Spawn(self,i);
|
|
}
|
|
}
|
|
void CheckUnderwaterAmb( bool restore = false )
|
|
{
|
|
Vector3 headpos = Vec3Offset(0,0,player.viewheight);
|
|
Vector3 centerpos = Vec3Offset(0,0,height/2);
|
|
Sector headregion = null;
|
|
if ( CurSector.moreflags&Sector.SECMF_UNDERWATER ) // check underwater sector
|
|
headregion = CurSector;
|
|
else if ( CurSector.heightsec && (Cursector.heightsec.moreflags&Sector.SECMF_UNDERWATER) ) // check height transfer
|
|
{
|
|
let hsec = CurSector.heightsec;
|
|
double fh = hsec.floorplane.ZAtPoint(pos.xy);
|
|
if ( pos.z < fh )
|
|
{
|
|
if ( headpos.z <= fh )
|
|
headregion = hsec;
|
|
}
|
|
else if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY) && (headpos.z > hsec.ceilingplane.ZAtPoint(pos.xy)) )
|
|
headregion = hsec;
|
|
}
|
|
else // check 3D floors
|
|
{
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
let ff = CurSector.Get3DFloor(i);
|
|
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || (!(ff.flags&F3DFloor.FF_SWIMMABLE) && (ff.alpha == 0)) ) continue;
|
|
double ff_bottom = ff.bottom.ZAtPoint(pos.xy);
|
|
double ff_top = ff.top.ZAtPoint(pos.xy);
|
|
if ( (ff_top <= pos.z) || (ff_bottom > centerpos.z) ) continue;
|
|
if ( headpos.z <= ff_top )
|
|
headregion = ff.model;
|
|
break;
|
|
}
|
|
}
|
|
int curunder = UNDER_NONE;
|
|
if ( headregion )
|
|
{
|
|
switch ( headregion.damagetype )
|
|
{
|
|
case 'Fire':
|
|
case 'Lava':
|
|
curunder = UNDER_LAVA;
|
|
break;
|
|
case 'Slime':
|
|
case 'Poison':
|
|
case 'PoisonCloud':
|
|
curunder = UNDER_SLIME;
|
|
break;
|
|
case 'Ice':
|
|
case 'Drowning':
|
|
default:
|
|
curunder = UNDER_WATER;
|
|
break;
|
|
}
|
|
undercol = headregion.ColorMap.LightColor;
|
|
}
|
|
if ( (curunder != lastunder) || restore )
|
|
{
|
|
static const string undersnd[] = {"","misc/underwater","misc/underslime","misc/underlava"};
|
|
static const string entersnd[] = {"","misc/waterenter","misc/slimeenter","misc/lavaenter"};
|
|
static const string exitsnd[] = {"","misc/waterexit","misc/slimeexit","misc/lavaexit"};
|
|
A_StopSound(CHAN_AMBEXTRA);
|
|
if ( curunder > UNDER_NONE )
|
|
{
|
|
A_StartSound(undersnd[curunder],CHAN_AMBEXTRA,CHANF_LOOP|CHANF_UI);
|
|
if ( !restore && (players[consoleplayer].Camera == self) )
|
|
A_StartSound(entersnd[curunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
|
|
}
|
|
if ( !restore && (lastunder > UNDER_NONE) && (players[consoleplayer].Camera == self) )
|
|
A_StartSound(exitsnd[lastunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
|
|
}
|
|
if ( curunder > UNDER_NONE )
|
|
A_SoundVolume(CHAN_AMBEXTRA,(players[consoleplayer].Camera==self)?1.:0.);
|
|
lastunder = curunder;
|
|
}
|
|
override Vector2 BobWeapon( double ticfrac )
|
|
{
|
|
// non-mod weapons bob normally
|
|
if ( !(player.ReadyWeapon is 'SWWMWeapon') )
|
|
return Super.BobWeapon(ticfrac);
|
|
bool oldbob = !!(player.WeaponState&WF_WEAPONBOBBING);
|
|
player.WeaponState |= WF_WEAPONBOBBING; // always bob
|
|
Vector2 cur = Super.BobWeapon(ticfrac);
|
|
if ( !oldbob ) player.WeaponState &= ~WF_WEAPONBOBBING;
|
|
double fangle = oldangle*(1.-ticfrac)+angle*ticfrac;
|
|
double fpitch = (oldpitch*(1.-ticfrac)+pitch*ticfrac);
|
|
double flagangle = (oldlagangle*(1.-ticfrac)+lagangle*ticfrac);
|
|
double flagpitch = (oldlagpitch*(1.-ticfrac)+lagpitch*ticfrac);
|
|
double diffang = fangle-flagangle;
|
|
double diffpitch = fpitch-flagpitch;
|
|
if ( abs(diffang) > 1. )
|
|
{
|
|
int sgn = (diffang>0)?1:-1;
|
|
diffang = abs(diffang)**.7*sgn;
|
|
}
|
|
if ( abs(diffpitch) > 1. )
|
|
{
|
|
int sgn = (diffpitch>0)?1:-1;
|
|
diffpitch = abs(diffpitch)**.7*sgn;
|
|
}
|
|
Vector3 flagvel = oldlagvel*(1.-ticfrac)+lagvel*ticfrac;
|
|
double diffx = flagvel dot (cos(flagangle+90),sin(flagangle+90),0);
|
|
double diffy = flagvel dot (0,0,1);
|
|
if ( abs(diffx) > 1. )
|
|
{
|
|
int sgn = (diffx>0)?1:-1;
|
|
diffx = abs(diffx)**.5*sgn;
|
|
}
|
|
if ( abs(diffy) > 1. )
|
|
{
|
|
int sgn = (diffy>0)?1:-1;
|
|
diffy = abs(diffy)**.5*sgn;
|
|
}
|
|
// don't do inertial sway when in 6dof mode, causes issues
|
|
if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',player);
|
|
if ( !fly6dof.GetBool() || !((waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2)) )
|
|
{
|
|
cur.x += diffang;
|
|
cur.y -= diffpitch;
|
|
cur.x += diffx*4.;
|
|
cur.y += diffy*4.;
|
|
}
|
|
return cur*(oldlagready*(1.-ticfrac)+lagready*ticfrac);
|
|
}
|
|
override void PlayerThink()
|
|
{
|
|
oldangle = angle;
|
|
oldpitch = pitch;
|
|
Super.PlayerThink();
|
|
oldlagangle = lagangle;
|
|
oldlagpitch = lagpitch;
|
|
lagangle = lagangle*.8+angle*.2;
|
|
lagpitch = lagpitch*.8+pitch*.2;
|
|
if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) )
|
|
{
|
|
// faster falloff
|
|
player.jumptics -= 5;
|
|
if ( player.onground && (player.jumptics < -18) )
|
|
player.jumptics = 0;
|
|
}
|
|
// quick grenade
|
|
if ( (player.playerstate != PST_DEAD) && (player.cmd.buttons&BT_USER4) )
|
|
SWWMGesture.SetGesture(self,GS_Grenade);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Vector3 oldpos = pos;
|
|
Super.Tick();
|
|
// can't be poisoned
|
|
PoisonDurationReceived = 0;
|
|
PoisonPeriodReceived = 0;
|
|
PoisonDamageReceived = 0;
|
|
if ( !player ) return;
|
|
if ( !selflight )
|
|
{
|
|
selflight = Spawn("DemolitionistSelfLight",pos);
|
|
selflight.target = self;
|
|
selflight.Tick();
|
|
}
|
|
// strife thing
|
|
if ( mystats.oldlogtext == "" )
|
|
mystats.oldlogtext = player.logtext;
|
|
else if ( player.logtext != mystats.oldlogtext )
|
|
{
|
|
mystats.questbacklog.Push(mystats.oldlogtext);
|
|
mystats.oldlogtext = player.logtext;
|
|
}
|
|
// overheal fading
|
|
if ( (health <= 200) || (health > oldhealth) )
|
|
{
|
|
healtimer = 0;
|
|
healcooldown = 80;
|
|
}
|
|
else if ( health > 200 )
|
|
{
|
|
if ( healcooldown > 0 ) healcooldown--;
|
|
else
|
|
{
|
|
if ( health > 500 )
|
|
{
|
|
if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%5) )
|
|
A_SetHealth(health-1);
|
|
}
|
|
else if ( health > 200 )
|
|
{
|
|
if ( !FindInventory("RefresherRegen") && !(healtimer%15) )
|
|
A_SetHealth(health-1);
|
|
}
|
|
healtimer++;
|
|
}
|
|
}
|
|
oldhealth = health;
|
|
oldlagvel = lagvel;
|
|
oldlagready = lagready;
|
|
if ( player.weaponstate&WF_WEAPONBOBBING ) lagready = lagready*.9+.1;
|
|
else lagready = lagready*.4;
|
|
lagvel = lagvel*.8+vel*.2;
|
|
double traveldist = level.Vec3Diff(oldpos,pos).length();
|
|
if ( waterlevel < 2 )
|
|
{
|
|
if ( !player.onground || bNoGravity )
|
|
{
|
|
cairtime++;
|
|
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
|
|
mystats.airdist += traveldist;
|
|
}
|
|
else
|
|
{
|
|
cairtime = 0;
|
|
mystats.grounddist += traveldist;
|
|
}
|
|
}
|
|
else mystats.swimdist += traveldist;
|
|
CheckUnderwaterAmb();
|
|
if ( player.cmd.buttons&BT_USER3 ) SenseItems();
|
|
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
|
|
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
|
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
|
|
if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) )
|
|
{
|
|
// bump down weapon
|
|
bumpdown = true;
|
|
bumpvelz = -lastvelz;
|
|
if ( lastvelz < -25 )
|
|
{
|
|
let s = Spawn("DemolitionistShockwave",pos);
|
|
s.target = self;
|
|
s.special1 = int(-lastvelz);
|
|
ReactionTime = 17;
|
|
A_Stop();
|
|
A_AlertMonsters(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);
|
|
}
|
|
mystats.stompcount++;
|
|
}
|
|
if ( lastvelz < -10 ) A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
|
if ( (player == players[consoleplayer]) && (lastvelz < -gruntspeed) && (mute.GetInt() < 4) && (health > 0) )
|
|
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
|
if ( lastvelz < -1 )
|
|
A_Footstep(0,true,clamp(-lastvelz*0.05,0.0,1.0));
|
|
}
|
|
// 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) )
|
|
{
|
|
// 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);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
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.bNOBLOOD && !encroached.bINVULNERABLE )
|
|
{
|
|
encroached.TraceBleed(realdmg,self);
|
|
encroached.SpawnBlood(pos,angle,realdmg);
|
|
}
|
|
}
|
|
}
|
|
if ( bumpdown )
|
|
{
|
|
lagvel.z += bumpvelz*.2;
|
|
bumpvelz *= .8;
|
|
if ( bumpvelz < double.epsilon ) bumpdown = false;
|
|
}
|
|
lastground = player.onground;
|
|
lastvelz = prevvelz;
|
|
prevvelz = vel.z;
|
|
bool isdashing = InStateSequence(CurState,FindState("Dash"));
|
|
bool isboosting = InStateSequence(CurState,FindState("Jump"));
|
|
bNOFRICTION = (((waterlevel<2)&&(bFly&&!bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||isdashing);
|
|
fuelcooldown = max(0,fuelcooldown-1);
|
|
dashcooldown = max(0,dashcooldown-1);
|
|
boostcooldown = max(0,boostcooldown-1);
|
|
if ( fuelcooldown <= 0 )
|
|
dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.));
|
|
if ( (dashboost > 0.) && (isdashing || (isboosting && player.cmd.buttons&BT_JUMP)) )
|
|
dashsnd = true;
|
|
else
|
|
{
|
|
if ( dashsnd )
|
|
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
|
|
dashsnd = false;
|
|
}
|
|
PainChance = isdashing?0:255;
|
|
if ( isdashing || (vel.length() > 50) )
|
|
{
|
|
Actor a;
|
|
if ( isdashing && (dashboost > 0.) )
|
|
{
|
|
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
|
|
{
|
|
a = Spawn("DashTrail",Vec3Angle(15,angle+i*140,35));
|
|
a.target = self;
|
|
a.vel = (RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
|
|
a.vel -= vel*.5;
|
|
}
|
|
}
|
|
Vector3 dir = vel;
|
|
double spd = dir.length();
|
|
dir = dir/spd;
|
|
Vector3 viewdir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
// look for things we could potentially bump into
|
|
bool bumped = false;
|
|
let bi = BlockThingsIterator.Create(self,500);
|
|
let raging = RagekitPower(FindInventory("RagekitPower"));
|
|
double maxmass = max(mass*spd/40.,200);
|
|
if ( raging ) maxmass *= 2;
|
|
while ( (spd > 0) && bi.Next() )
|
|
{
|
|
a = bi.Thing;
|
|
if ( !a || (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS || a.bCORPSE ) continue;
|
|
if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*spd,8,16) ) continue;
|
|
if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
|
|
Vector3 diff = level.Vec3Diff(pos,a.pos);
|
|
Vector3 dirto = diff.unit();
|
|
if ( dir dot dirto < .1 ) continue;
|
|
if ( a.bACTLIKEBRIDGE && (diff.z <= -a.Height) ) continue; // don't bump bridges if hit by above
|
|
// large monsters will stop the player (unless hit from above if we're going at ground pound speed)
|
|
A_QuakeEx(4,4,4,10,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
|
|
A_AlertMonsters(swwm_uncapalert?0:800);
|
|
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
a.A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
bumptic = gametic+int(20+spd/4.);
|
|
if ( (diff.z < a.height) && (lastvelz >= -25) && (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;
|
|
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
|
|
vel *= .2;
|
|
vel -= dir*(10+(spd*30/mass));
|
|
vel -= dirto*(10+(spd*50/mass));
|
|
vel.z += 5+(spd*(10/mass));
|
|
dashboost *= 0.;
|
|
}
|
|
Vector3 pushdir = dirto*.1+dir*.9;
|
|
if ( !a.bDONTTHRUST && (a.Mass < maxmass) && (a.bSHOOTABLE || a.bPUSHABLE) )
|
|
{
|
|
a.vel += pushdir*(15+(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(15+spd*2.5);
|
|
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,CHAN_OVERLAP,1.,.2);
|
|
Spawn("SWWMItemFog",level.Vec3Offset(pos,diff/2));
|
|
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:1.);
|
|
mystats.buttslams++;
|
|
}
|
|
}
|
|
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(15+spd*2.5);
|
|
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(15+spd*2.5);
|
|
raging.DoHitFX();
|
|
}
|
|
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
busted = true;
|
|
if ( buttslam )
|
|
{
|
|
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.2);
|
|
Spawn("SWWMItemFog",Vec3Offset(0,0,Height));
|
|
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:1.);
|
|
mystats.buttslams++;
|
|
}
|
|
}
|
|
}
|
|
if ( !busted || !raging )
|
|
{
|
|
// headbump
|
|
bumped = true;
|
|
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
bumptic = gametic+int(20+spd/4.);
|
|
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
|
|
A_AlertMonsters(swwm_uncapalert?0:800);
|
|
vel *= .2;
|
|
vel -= dir*(10+(spd*30/mass));
|
|
vel.z += 5+(spd*(10/mass));
|
|
vel += ceilnorm*(15+(spd*40/mass));
|
|
dashboost *= 0.;
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
|
|
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
|
|
ps.target = self;
|
|
ps.special1 = int(15+spd*2.5);
|
|
raging.DoHitFX();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// check for wall collision
|
|
FCheckPosition tm;
|
|
Vector2 steppy = dir.xy*spd/8.;
|
|
Vector3 oldpos = pos;
|
|
bool oldthru = bTHRUACTORS;
|
|
bTHRUACTORS = true;
|
|
for ( int i=1; i<=8; i++ )
|
|
{
|
|
if ( (spd > 0) && !bumped && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) && SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKEVERYTHING|Line.ML_BLOCKING|Line.ML_BLOCK_PLAYERS) )
|
|
{
|
|
Vector3 wallnorm = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
int lside = 1;
|
|
if ( !BlockingLine.sidedef[1] || !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
|
|
{
|
|
lside = 0;
|
|
wallnorm *= -1;
|
|
}
|
|
// don't bump if we're only grazing it
|
|
if ( dir dot wallnorm > -.6 )
|
|
continue;
|
|
bool buttslam = false;
|
|
// BUTTSLAM
|
|
if ( dir dot viewdir < -.3 )
|
|
{
|
|
buttslam = true;
|
|
// leave buttmark
|
|
A_SprayDecal("ButtMark",172,(cos(angle+90)*2,sin(angle+90)*2,Height*.54),dir);
|
|
A_SprayDecal("ButtMark",172,(cos(angle-90)*2,sin(angle-90)*2,Height*.54),dir);
|
|
}
|
|
if ( raging || swwm_omnibust )
|
|
{
|
|
// see if we can bust it
|
|
let tempme = new("LineTracer"); // gross hack to pass needed data
|
|
int dmg = int(15+spd*2.5);
|
|
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/2) )
|
|
{
|
|
// 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(15+spd*2.5);
|
|
raging.DoHitFX();
|
|
}
|
|
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
if ( buttslam )
|
|
{
|
|
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.2);
|
|
Spawn("SWWMItemFog",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
|
|
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:1.);
|
|
mystats.buttslams++;
|
|
}
|
|
if ( raging ) continue; // don't stop
|
|
}
|
|
}
|
|
// wallbump
|
|
bumped = true;
|
|
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
bumptic = gametic+int(20+spd/4.);
|
|
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
|
|
A_AlertMonsters(swwm_uncapalert?0:800);
|
|
vel *= .2;
|
|
vel -= dir*(10+(spd*30/mass));
|
|
vel += wallnorm*(10+(spd*50/mass));
|
|
vel.z += 5+(spd*(10/mass));
|
|
dashboost *= 0.;
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,Height/2.));
|
|
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
|
|
ps.target = self;
|
|
ps.special1 = int(15+spd*2.5);
|
|
raging.DoHitFX();
|
|
}
|
|
// activate it
|
|
int locknum = SWWMUtility.GetLineLock(BlockingLine);
|
|
if ( !locknum || CheckKeys(locknum,false,true) )
|
|
BlockingLine.Activate(self,lside,SPAC_Use);
|
|
BlockingLine.Activate(self,lside,SPAC_Impact);
|
|
break;
|
|
}
|
|
}
|
|
SetOrigin(oldpos,true);
|
|
bTHRUACTORS = oldthru;
|
|
}
|
|
else if ( isboosting && (dashboost > 0.) )
|
|
{
|
|
Actor a;
|
|
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
|
|
{
|
|
a = Spawn("DashTrail",Vec3Angle(10,angle+i*140,40));
|
|
a.target = self;
|
|
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
|
|
a.vel -= vel*.5;
|
|
}
|
|
}
|
|
}
|
|
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
|
|
{
|
|
if ( (swwm_strictuntouchable >= 2) && (damage > 0) && player )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.tookdamage[PlayerNumber()] = true;
|
|
}
|
|
// no damage whatsoever
|
|
if ( scriptedinvul )
|
|
return 0;
|
|
if ( damage <= 0 )
|
|
{
|
|
lastdamage = 0;
|
|
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
|
|
}
|
|
// lucky collar
|
|
if ( (source == self) ) damage = max(1,damage/2);
|
|
if ( (Health-damage < 25) )
|
|
{
|
|
int splitdmg[2];
|
|
splitdmg[0] = max(0,Health-25); // non-reduced part (>=25% health)
|
|
splitdmg[1] = max(1,(damage-splitdmg[0])/4); // reduced part (<25% health)
|
|
damage = splitdmg[0]+splitdmg[1];
|
|
}
|
|
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;
|
|
lastdamage = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
|
|
lastdamagetic = max(lastdamagetic,gametic+clamp(lastdamage/2,10,40));
|
|
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
|
|
{
|
|
lastmpain = level.maptime;
|
|
A_DemoPain();
|
|
}
|
|
PainChance = oldpchance;
|
|
return lastdamage;
|
|
}
|
|
override void CalcHeight()
|
|
{
|
|
Super.CalcHeight();
|
|
// handle smooth step down (hacky but looks ok)
|
|
player.viewz += ssup;
|
|
ssup = max(0,(ssup*.7)-.25);
|
|
}
|
|
override void CheckPitch()
|
|
{
|
|
if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
|
|
return; // handled in moveplayer
|
|
Super.CheckPitch();
|
|
}
|
|
override void CheckMoveUpDown()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash")) )
|
|
player.cmd.upmove = 0;
|
|
if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
|
|
{
|
|
double fs = TweakSpeeds(1.,0.);
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 accel;
|
|
if ( (player.cmd.upmove == -32768) || sendtoground )
|
|
{
|
|
sendtoground = true;
|
|
player.centering = true;
|
|
accel = (0,0,-4096);
|
|
}
|
|
else accel = z*player.cmd.upmove*8.;
|
|
accel *= fs/128.;
|
|
vel = vel+accel/TICRATE;
|
|
if ( sendtoground ) vel.xy *= .6;
|
|
if ( (pos.z <= floorz) || bOnMobj ) sendtoground = false;
|
|
if ( vel.length() > 50. )
|
|
vel = vel.unit()*50.;
|
|
return;
|
|
}
|
|
else sendtoground = false;
|
|
Super.CheckMoveUpDown();
|
|
}
|
|
override void MovePlayer()
|
|
{
|
|
if ( !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
|
|
{
|
|
dashboost = 0.;
|
|
if ( dashsnd ) A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
|
|
dashsnd = false;
|
|
Super.MovePlayer();
|
|
return;
|
|
}
|
|
bool isdashing = InStateSequence(CurState,FindState("Dash"));
|
|
if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0;
|
|
if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
|
|
{
|
|
player.onground = false;
|
|
if ( player.turnticks )
|
|
{
|
|
player.turnticks--;
|
|
guideangle = (180./TURN180_TICKS);
|
|
}
|
|
else guideangle += .2*player.cmd.yaw*(360./65536.);
|
|
guidepitch -= .2*player.cmd.pitch*(360./65536.);
|
|
if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',player);
|
|
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
|
|
if ( player.centering || !fly6dof.GetBool() ) guideroll = clamp(deltaangle(roll,0),-3.,3.);
|
|
if ( fly6dof.GetBool() )
|
|
{
|
|
swwm_Quat orient = swwm_Quat.create_euler(pitch,angle,roll);
|
|
swwm_Quat angles = swwm_Quat.create_euler(guidepitch,guideangle,guideroll);
|
|
orient = orient.qmul(angles);
|
|
double npitch, nangle, nroll;
|
|
[npitch, nangle, nroll] = orient.to_euler();
|
|
A_SetAngle(nangle,SPF_INTERPOLATE);
|
|
A_SetPitch(npitch,SPF_INTERPOLATE);
|
|
A_SetRoll(nroll,SPF_INTERPOLATE);
|
|
}
|
|
else
|
|
{
|
|
A_SetAngle(angle+guideangle,SPF_INTERPOLATE);
|
|
A_SetPitch(clamp(pitch+guidepitch,player.MinPitch,player.MaxPitch),SPF_INTERPOLATE);
|
|
A_SetRoll(roll+guideroll,SPF_INTERPOLATE);
|
|
}
|
|
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
|
|
{
|
|
guideroll = guidepitch = roll = pitch = 0.;
|
|
player.centering = false;
|
|
}
|
|
double fs = TweakSpeeds(1.,0.);
|
|
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);
|
|
Vector3 accel = x*player.cmd.forwardmove+y*player.cmd.sidemove+z*jcmove;
|
|
accel *= fs/320.;
|
|
double spd = vel.length();
|
|
if ( spd > 40. ) vel = (vel+accel/TICRATE).unit()*spd;
|
|
else vel = vel+accel/TICRATE;
|
|
vel *= .97;
|
|
player.vel = (1,1)*vel.length();
|
|
player.jumptics = -2;
|
|
if ( !(player.cheats & CF_PREDICTING) && (player.cmd.forwardmove|player.cmd.sidemove) )
|
|
PlayRunning();
|
|
if ( player.cheats&CF_REVERTPLEASE )
|
|
{
|
|
player.cheats &= ~CF_REVERTPLEASE;
|
|
player.camera = player.mo;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Super.MovePlayer();
|
|
// override air control because we REALLY need the extra mobility
|
|
if ( !player.onground && !bNOGRAVITY && (waterlevel < 2) )
|
|
{
|
|
double fs = TweakSpeeds(1.,0.);
|
|
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
|
|
Vector3 accel = (RotateVector((player.cmd.forwardmove,-player.cmd.sidemove),angle),0);
|
|
accel *= fs/512.;
|
|
double spd = vel.length();
|
|
if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd;
|
|
else vel = vel+accel/TICRATE;
|
|
}
|
|
else if ( player.onground && !bNOGRAVITY && (waterlevel < 2) )
|
|
{
|
|
// quickly decelerate if we're not holding movement keys
|
|
if ( !(player.cmd.forwardmove|player.cmd.sidemove) ) vel *= .97;
|
|
}
|
|
if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE);
|
|
}
|
|
guideangle *= .9;
|
|
guidepitch *= .9;
|
|
guideroll *= .9;
|
|
// anchor to ground when going down steps
|
|
if ( lastground && !player.onground && !bFly && !bFlyCheat && (waterlevel < 2) && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z < 0) && !isdashing )
|
|
{
|
|
// test for gap crossing (i.e: climbing up platforms with holes between them)
|
|
Vector3 storepos = pos;
|
|
double storefloorz = floorz;
|
|
bool crossgap = false;
|
|
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
|
|
{
|
|
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),true);
|
|
if ( floorz < storepos.z ) continue;
|
|
crossgap = true;
|
|
break;
|
|
}
|
|
SetOrigin(storepos,true);
|
|
floorz = storefloorz;
|
|
if ( !crossgap )
|
|
{
|
|
ssup = max(0,(pos.z-floorz));
|
|
SetZ(floorz);
|
|
lastground = player.onground = true;
|
|
}
|
|
}
|
|
if ( player.onground ) lastgroundtic = level.maptime;
|
|
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) ) dodge.xy = RotateVector((1,0),angle);
|
|
if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z);
|
|
if ( player.onground ) dodge.z = max(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) )
|
|
{
|
|
dashdir = dodge.unit();
|
|
dashcooldown = 10;
|
|
dashboost = 20.;
|
|
bOnMobj = false;
|
|
if ( player.cheats & CF_REVERTPLEASE )
|
|
{
|
|
player.cheats &= ~CF_REVERTPLEASE;
|
|
player.camera = player.mo;
|
|
}
|
|
vel.z += clamp(-vel.z*.4,0.,30.);
|
|
player.jumptics = -1;
|
|
SetStateLabel("Dash");
|
|
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
|
|
mystats.dashcount++;
|
|
}
|
|
}
|
|
override void CheckJump()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
|
|
Vector2 walldir = (cos(angle),sin(angle));
|
|
bool walljump = false, wallclimb = false;
|
|
FLineTraceData d;
|
|
Actor jumpactor = null;
|
|
for ( int i=-4; i<int(height*.8); i++ )
|
|
{
|
|
if ( !wallclimb )
|
|
{
|
|
for ( int k=0; k<30; k+=10 )
|
|
{
|
|
for ( int j=-1; j<=1; j+=2 )
|
|
{
|
|
double ang = angle+k*j;
|
|
wallclimb = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
|
|
if ( wallclimb )
|
|
{
|
|
jumpactor = d.HitActor;
|
|
walldir = (walldir*.7+(cos(ang),sin(ang))*.3);
|
|
break;
|
|
}
|
|
}
|
|
if ( wallclimb ) break;
|
|
}
|
|
if ( wallclimb ) break;
|
|
}
|
|
if ( wallclimb ) break;
|
|
if ( !walljump )
|
|
{
|
|
for ( int k=0; k<60; k+=10 )
|
|
{
|
|
for ( int j=-1; j<=1; j+=2 )
|
|
{
|
|
double ang = (angle-180)+k*j;
|
|
walljump = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
|
|
if ( walljump )
|
|
{
|
|
jumpactor = d.HitActor;
|
|
walldir = (walldir*.7+(cos(ang+180),sin(ang+180))*.3);
|
|
break;
|
|
}
|
|
}
|
|
if ( walljump ) break;
|
|
}
|
|
if ( walljump ) break;
|
|
}
|
|
if ( walljump ) break;
|
|
}
|
|
if ( (player.cmd.buttons&BT_JUMP) && (gamestate == GS_LEVEL) )
|
|
{
|
|
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
|
|
if ( level.maptime < (lastgroundtic+4) )
|
|
{
|
|
walljump = false;
|
|
wallclimb = false;
|
|
jumpactor = null;
|
|
}
|
|
if ( player.crouchoffset ) player.crouching = 1;
|
|
else if ( waterlevel >= 2 ) vel.z = 4*Speed;
|
|
else if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return;
|
|
else if ( bNoGravity ) vel.z = 3;
|
|
else if ( level.IsJumpingAllowed()
|
|
&& ((player.onground && (player.jumptics == 0))
|
|
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) )
|
|
{
|
|
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
|
|
return;
|
|
double jumpvelz = JumpZ*35./TICRATE;
|
|
double jumpfac = 0;
|
|
for ( let p=Inv; p; p=p.Inv )
|
|
{
|
|
let pp = PowerHighJump(p);
|
|
if ( pp )
|
|
{
|
|
double f = pp.Strength;
|
|
if ( f > jumpfac ) jumpfac = f;
|
|
}
|
|
}
|
|
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
|
|
if ( FindInventory("RagekitPower") ) 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 += jumpvelz+clamp(-pvelz*.6,0.,30.);
|
|
vel.xy += walldir*20*Speed;
|
|
}
|
|
else if ( wallclimb )
|
|
{
|
|
if ( vel.z < 10. )
|
|
vel.z += jumpvelz+clamp(-pvelz*.8,0.,30.);
|
|
vel.xy -= walldir*5*Speed;
|
|
}
|
|
if ( jumpactor && jumpactor.bSHOOTABLE )
|
|
{
|
|
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
|
|
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
|
|
if ( FindInventory("RagekitPower") )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",pos);
|
|
ps.damagetype = 'Jump';
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
}
|
|
if ( walljump || wallclimb )
|
|
{
|
|
A_StartSound("demolitionist/kick",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
|
last_kick = level.maptime+1;
|
|
}
|
|
}
|
|
bOnMobj = false;
|
|
player.jumpTics = -1;
|
|
if ( !(player.cheats&CF_PREDICTING) )
|
|
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
|
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
|
|
{
|
|
dashboost = 3.;
|
|
boostcooldown = 20;
|
|
if ( vel.z < 10. )
|
|
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
|
|
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
|
|
mystats.boostcount++;
|
|
last_boost = level.maptime+1;
|
|
}
|
|
else
|
|
{
|
|
dashboost = 0.;
|
|
if ( vel.z < 10. )
|
|
vel.z += jumpvelz*1.25;
|
|
}
|
|
SetStateLabel("Jump");
|
|
}
|
|
last_jump_held = level.maptime+1;
|
|
}
|
|
}
|
|
override void DeathThink()
|
|
{
|
|
player.Uncrouch();
|
|
TickPSprites();
|
|
player.onground = (pos.Z<=floorz);
|
|
// ded (demo-chan falls faster tho)
|
|
player.deltaviewheight = 0;
|
|
if ( player.viewheight > 6 ) player.viewheight -= 3;
|
|
if ( player.viewheight < 6 ) player.viewheight = 6;
|
|
// center pitch
|
|
double dpitch = clamp(deltaangle(pitch,0),-6,6);
|
|
if ( abs(dpitch) < 3. ) pitch = 0.;
|
|
else 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--;
|
|
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) && !player.Bot) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn && (Level.maptime >= player.respawn_time))) && !sv_norespawn )
|
|
{
|
|
player.cls = null;
|
|
player.playerstate = PST_REBORN;
|
|
if ( special1 > 2 ) special1 = 0;
|
|
}
|
|
}
|
|
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) )
|
|
{
|
|
// reload save
|
|
player.cls = null;
|
|
player.playerstate = PST_ENTER;
|
|
if ( special1 > 2 ) special1 = 0;
|
|
}
|
|
else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 120) && swwm_revive )
|
|
{
|
|
// reboot (if possible)
|
|
if ( !FindInventory("ReviveCooldown") )
|
|
{
|
|
player.Resurrect();
|
|
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);
|
|
}
|
|
SWWMHandler.DoFlash(self,Color(255,255,255,255),10);
|
|
SWWMHandler.DoFlash(self,Color(255,128,192,255),30);
|
|
SWWMScoreObj.Spawn(default.health,Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Height/2),Font.CR_BLUE);
|
|
if ( special1 > 2 ) special1 = 0;
|
|
if ( swwm_revivecooldown > 0 )
|
|
GiveInventory("ReviveCooldown",1);
|
|
}
|
|
else if ( level.maptime > revivefail )
|
|
{
|
|
if ( player == players[consoleplayer] ) A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI);
|
|
revivefail = level.maptime+120;
|
|
}
|
|
}
|
|
}
|
|
else deadtimer = 0;
|
|
}
|
|
override void PlayIdle()
|
|
{
|
|
if ( !player )
|
|
{
|
|
if ( !InStateSequence(CurState,FindState("Spawn")) )
|
|
SetStateLabel("Spawn");
|
|
return;
|
|
}
|
|
if ( player.health <= 0 ) return;
|
|
if ( !bNoGravity && player.onground && (waterlevel < 3) )
|
|
{
|
|
// Ground
|
|
if ( player.crouchdir == -1 )
|
|
{
|
|
// Crouching
|
|
if ( InStateSequence(CurState,FindState("CrouchMove")) )
|
|
SetStateLabel("Crouch");
|
|
else if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("SeeFast"))
|
|
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
|
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
|
|| InStateSequence(CurState,FindState("Float"))
|
|
|| InStateSequence(CurState,FindState("Swim"))
|
|
|| InStateSequence(CurState,FindState("SwimLoop")) )
|
|
SetStateLabel("StartCrouch");
|
|
}
|
|
else
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Crouch"))
|
|
|| InStateSequence(CurState,FindState("CrouchMove")) )
|
|
SetStateLabel("EndCrouch");
|
|
else if ( InStateSequence(CurState,FindState("See"))
|
|
|| 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")) )
|
|
SetStateLabel("SwimEnd");
|
|
}
|
|
}
|
|
else if ( !bNoGravity && (waterlevel < 1) )
|
|
{
|
|
// Falling
|
|
if ( player.crouchdir == -1 )
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("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")) )
|
|
SetStateLabel("StartCrouch");
|
|
}
|
|
else
|
|
{
|
|
if ( (InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("SeeFast"))
|
|
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
|
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
|
|| InStateSequence(CurState,FindState("Float")))
|
|
&& (abs(pos.z-floorz) > maxstepheight) )
|
|
SetStateLabel("Fall");
|
|
else if ( InStateSequence(CurState,FindState("Crouch"))
|
|
|| InStateSequence(CurState,FindState("CrouchMove")) )
|
|
SetStateLabel("EndCrouch");
|
|
else if ( InStateSequence(CurState,FindState("Swim"))
|
|
|| InStateSequence(CurState,FindState("SwimLoop")) )
|
|
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("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")) )
|
|
SetStateLabel("StartCrouch");
|
|
}
|
|
else
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("SeeFast"))
|
|
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
|
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
|
|| InStateSequence(CurState,FindState("Jump"))
|
|
|| InStateSequence(CurState,FindState("Fall"))
|
|
|| InStateSequence(CurState,FindState("FallLoop")) )
|
|
SetStateLabel("Float");
|
|
else if ( InStateSequence(CurState,FindState("Swim"))
|
|
|| InStateSequence(CurState,FindState("SwimLoop")) )
|
|
SetStateLabel("SwimEnd");
|
|
else if ( InStateSequence(CurState,FindState("Crouch"))
|
|
|| InStateSequence(CurState,FindState("CrouchMove")) )
|
|
SetStateLabel("EndCrouch");
|
|
}
|
|
}
|
|
}
|
|
override void PlayRunning()
|
|
{
|
|
if ( !player )
|
|
{
|
|
if ( !InStateSequence(CurState,FindState("See")) )
|
|
SetStateLabel("See");
|
|
return;
|
|
}
|
|
if ( player.health <= 0 ) return;
|
|
if ( !bNoGravity && player.onground && (waterlevel < 3) )
|
|
{
|
|
// Ground
|
|
if ( player.crouchdir == -1 )
|
|
{
|
|
// Crouching
|
|
if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("SeeFast"))
|
|
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
|
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
|
|| InStateSequence(CurState,FindState("Fall"))
|
|
|| InStateSequence(CurState,FindState("FallLoop"))
|
|
|| InStateSequence(CurState,FindState("Float"))
|
|
|| InStateSequence(CurState,FindState("Swim"))
|
|
|| InStateSequence(CurState,FindState("SwimLoop")) )
|
|
SetStateLabel("StartCrouch");
|
|
else if ( InStateSequence(CurState,FindState("Crouch")) )
|
|
SetStateLabel("CrouchMove");
|
|
}
|
|
else
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Crouch"))
|
|
|| InStateSequence(CurState,FindState("CrouchMove")) )
|
|
SetStateLabel("EndCrouch");
|
|
else if ( InStateSequence(CurState,FindState("Swim"))
|
|
|| InStateSequence(CurState,FindState("SwimLoop")) )
|
|
SetStateLabel("SwimEnd");
|
|
else if ( FastCheck()
|
|
&& (InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))) )
|
|
SetStateLabel("SeeFast");
|
|
else if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Float"))
|
|
|| InStateSequence(CurState,FindState("Turn")) )
|
|
{
|
|
SetStateLabel("See");
|
|
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
|
}
|
|
}
|
|
}
|
|
else if ( !bNoGravity && (waterlevel < 1) )
|
|
{
|
|
// Falling
|
|
PlayIdle();
|
|
}
|
|
else
|
|
{
|
|
// Swimming
|
|
if ( player.crouchdir == -1 )
|
|
{
|
|
// Crouching
|
|
if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("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")) )
|
|
SetStateLabel("StartCrouch");
|
|
else if ( InStateSequence(CurState,FindState("Crouch")) )
|
|
SetStateLabel("CrouchMove");
|
|
}
|
|
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("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")) )
|
|
SetStateLabel("Float");
|
|
else if ( InStateSequence(CurState,FindState("Crouch"))
|
|
|| InStateSequence(CurState,FindState("CrouchMove")) )
|
|
SetStateLabel("EndCrouch");
|
|
}
|
|
else
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Spawn"))
|
|
|| InStateSequence(CurState,FindState("Turn"))
|
|
|| InStateSequence(CurState,FindState("See"))
|
|
|| InStateSequence(CurState,FindState("SeeFast"))
|
|
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|
|
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|
|
|| InStateSequence(CurState,FindState("Jump"))
|
|
|| InStateSequence(CurState,FindState("Fall"))
|
|
|| InStateSequence(CurState,FindState("FallLoop"))
|
|
|| InStateSequence(CurState,FindState("Float")) )
|
|
SetStateLabel("Swim");
|
|
else if ( InStateSequence(CurState,FindState("Crouch"))
|
|
|| InStateSequence(CurState,FindState("CrouchMove")) )
|
|
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("Jump")) )
|
|
return; // don't cancel dash/jump
|
|
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
|
|
else SetStateLabel("Missile");
|
|
}
|
|
override void PlayAttacking2()
|
|
{
|
|
PlayAttacking();
|
|
}
|
|
void PlayFire()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash"))
|
|
|| InStateSequence(CurState,FindState("Jump")) )
|
|
return; // don't cancel dash/jump
|
|
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
|
|
else SetStateLabel("Missile");
|
|
}
|
|
void PlayMelee()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash"))
|
|
|| InStateSequence(CurState,FindState("Jump")) )
|
|
return; // don't cancel dash/jump
|
|
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMelee");
|
|
else SetStateLabel("Melee");
|
|
}
|
|
void PlayFastMelee()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash"))
|
|
|| InStateSequence(CurState,FindState("Jump")) )
|
|
return; // don't cancel dash/jump
|
|
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastMelee");
|
|
else SetStateLabel("FastMelee");
|
|
}
|
|
void PlayReload()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash"))
|
|
|| InStateSequence(CurState,FindState("Jump")) )
|
|
return; // don't cancel dash/jump
|
|
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchReload");
|
|
else SetStateLabel("Reload");
|
|
}
|
|
void PlayCheckGun()
|
|
{
|
|
if ( InStateSequence(CurState,FindState("Dash"))
|
|
|| InStateSequence(CurState,FindState("Jump")) )
|
|
return; // don't cancel dash/jump
|
|
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 ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
|
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
|
|
if ( lastdamage > 90 )
|
|
{
|
|
A_StartSound("demolitionist/hipain",CHAN_VOICE);
|
|
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
|
|
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
|
|
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
|
}
|
|
else if ( lastdamage > 30 )
|
|
{
|
|
A_StartSound("demolitionist/pain",CHAN_VOICE);
|
|
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
|
|
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
|
|
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
|
}
|
|
else if ( lastdamage > 0 )
|
|
{
|
|
A_StartSound("demolitionist/lopain",CHAN_VOICE);
|
|
if ( !player || (player.mo != self) || (lastdamage < 5) ) return; // voodoo dolls have no voice
|
|
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
|
|
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
|
}
|
|
}
|
|
void A_DemoScream()
|
|
{
|
|
A_StopSound(CHAN_DEMOVOICE);
|
|
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
|
|
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
|
|
Sound snd = "demolitionist/death";
|
|
if ( special1 < 10 )
|
|
snd = "demolitionist/wdeath";
|
|
if ( health < 50 )
|
|
snd = "demolitionist/xdeath";
|
|
A_StartSound(snd,CHAN_VOICE);
|
|
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
|
|
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
|
|
A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
|
|
}
|
|
override bool OnGiveSecret( bool printmsg, bool playsound )
|
|
{
|
|
if ( !player ) return false;
|
|
int score = 100;
|
|
// last secret (this is called before counting it up, so have to subtract)
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
|
|
{
|
|
if ( hnd ) hnd.allsecrets = true;
|
|
score = 1000;
|
|
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),player.GetUserName(),score);
|
|
}
|
|
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score);
|
|
if ( CheckLocalView() ) SWWMHandler.AddOneliner("findsecret",2,40);
|
|
SWWMCredits.Give(player,score);
|
|
SWWMScoreObj.Spawn(score,Vec3Offset(0,0,Height/2));
|
|
// somehow ongivesecret can be called BEFORE PostBeginPlay (what the fuck)
|
|
if ( !mystats ) mystats = SWWMStats.Find(player);
|
|
mystats.secrets++;
|
|
return true;
|
|
}
|
|
override void AddInventory( Inventory item )
|
|
{
|
|
Super.AddInventory(item);
|
|
if ( !player ) return;
|
|
// add lore if any
|
|
SWWMLoreLibrary.Add(player,item.GetClassName());
|
|
if ( (item is 'Weapon') && !(item is 'SWWMGesture') && !(item is 'SWWMItemGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() )
|
|
SWWMHandler.AddOneliner("getweapon",2);
|
|
if ( (item is 'Key') && !key_reentrant && !deathmatch )
|
|
{
|
|
// score
|
|
int score = 100;
|
|
Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score);
|
|
SWWMCredits.Give(player,score);
|
|
SWWMScoreObj.Spawn(100,Vec3Offset(0,0,Height/2));
|
|
if ( !swwm_sharekeys ) return;
|
|
// share all keys in mp
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] || !players[i].mo || (i == PlayerNumber()) )
|
|
continue;
|
|
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = true;
|
|
let tkey = Inventory(Spawn(item.GetClass()));
|
|
SWWMHandler.KeyTagFix(tkey);
|
|
if ( tkey is 'SWWMKey' ) SWWMKey(tkey).propagated = true; // no anim
|
|
if ( !tkey.CallTryPickup(self) ) tkey.Destroy();
|
|
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = false;
|
|
}
|
|
}
|
|
// add collectible to stats
|
|
if ( (item is 'SWWMCollectible') && mystats )
|
|
{
|
|
let cls = item.GetClass();
|
|
if ( (mystats.ownedcollectibles.Size() > 0) && (mystats.ownedcollectibles.Find(cls) < mystats.ownedcollectibles.Size()) ) return;
|
|
mystats.ownedcollectibles.Push(cls);
|
|
}
|
|
}
|
|
override bool UseInventory( Inventory item )
|
|
{
|
|
let itemtype = item.GetClass();
|
|
if ( (player.cheats&CF_TOTALLYFROZEN) || isFrozen() ) return false;
|
|
if ( !Actor.UseInventory(item) )
|
|
{
|
|
if ( player == players[consoleplayer] )
|
|
{
|
|
if ( !(item is 'Weapon') )
|
|
A_StartSound("menu/noinvuse",CHAN_ITEMEXTRA);
|
|
if ( item is 'PuzzleItem' )
|
|
SWWMHandler.AddOneliner("puzzfail",2,20);
|
|
}
|
|
return false;
|
|
}
|
|
// use sounds of big powerups are heard by other players
|
|
if ( (player == players[consoleplayer]) || item.bBIGPOWERUP )
|
|
A_StartSound(item.UseSound,CHAN_ITEMEXTRA);
|
|
if ( (player == players[consoleplayer]) && (item is 'PuzzleItem') )
|
|
SWWMHandler.AddOneliner("puzzsucc",2,10);
|
|
return true;
|
|
}
|
|
void A_Footstep( double yofs, bool run = false, double vol = .3 )
|
|
{
|
|
if ( run )
|
|
{
|
|
A_StartSound("demolitionist/run",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
|
|
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
|
|
b.A_CheckTerrain();
|
|
}
|
|
else
|
|
{
|
|
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
|
|
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
|
|
b.A_CheckTerrain();
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
bool FastCheck( bool notfast = false )
|
|
{
|
|
if ( !player ) return false;
|
|
bool rslt = player.cmd.buttons&BT_RUN;
|
|
if ( notfast ) rslt = !rslt;
|
|
return rslt;
|
|
}
|
|
bool AllowCrouch()
|
|
{
|
|
if ( player.cmd.buttons&BT_JUMP ) return false;
|
|
if ( InStateSequence(CurState,FindState("Dash")) ) return false; // no crouch during dash
|
|
return true;
|
|
}
|
|
// Imagine having to duplicate two functions only to change a couple values in both
|
|
// I sure love constants
|
|
override void CrouchMove( int direction )
|
|
{
|
|
double defaultheight = FullHeight;
|
|
double savedheight = Height;
|
|
double crouchspeed = direction*CROUCHSPEED*.8; // slow down slightly so it matches the animation
|
|
double oldheight = player.viewheight;
|
|
player.crouchdir = direction;
|
|
player.crouchfactor += crouchspeed;
|
|
// check whether the move is ok
|
|
Height = defaultheight; // actually test the full height, or it'll look weird
|
|
if ( !TryMove(pos.xy,false) )
|
|
{
|
|
Height = savedheight;
|
|
if ( direction > 0 )
|
|
{
|
|
// doesn't fit
|
|
player.crouchfactor -= crouchspeed;
|
|
player.crouchdir = -1; // force crouch
|
|
return;
|
|
}
|
|
}
|
|
Height = savedheight;
|
|
player.crouchfactor = clamp(player.crouchfactor,.3,1.);
|
|
player.viewheight = ViewHeight*player.crouchfactor;
|
|
player.crouchviewdelta = player.viewheight-ViewHeight;
|
|
// Check for eyes going above/below fake floor due to crouching motion.
|
|
CheckFakeFloorTriggers(pos.z+oldheight,true);
|
|
}
|
|
override void CheckCrouch( bool totallyfrozen )
|
|
{
|
|
// crouch to swim/float down
|
|
if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) )
|
|
{
|
|
if ( waterlevel >= 2 ) vel.z = -4*Speed;
|
|
else if ( bNOGRAVITY ) vel.z = -3;
|
|
}
|
|
bool wascrouching = !!(player.cmd.buttons&BT_CROUCH);
|
|
if ( !AllowCrouch() ) player.cmd.buttons &= ~BT_CROUCH;
|
|
if ( CanCrouch() && (player.health > 0) && level.IsCrouchingAllowed() )
|
|
{
|
|
if ( !totallyfrozen )
|
|
{
|
|
int crouchdir = player.crouching;
|
|
if ( !crouchdir ) crouchdir = (player.cmd.buttons&BT_CROUCH)?-1:1;
|
|
else if ( player.cmd.buttons&BT_CROUCH ) player.crouching = 0;
|
|
if ( (crouchdir == 1) && (player.crouchfactor < 1) && (pos.z+height < ceilingz) )
|
|
CrouchMove(1);
|
|
else if ( (crouchdir == -1) && (player.crouchfactor > .3) )
|
|
CrouchMove(-1);
|
|
}
|
|
}
|
|
else player.Uncrouch();
|
|
player.crouchoffset = -(ViewHeight)*(1-player.crouchfactor);
|
|
// we need the crouch button state to be preserved for other functions
|
|
if ( wascrouching ) player.cmd.buttons |= BT_CROUCH;
|
|
}
|
|
override void Travelled()
|
|
{
|
|
// reinitialize
|
|
dashfuel = default.dashfuel;
|
|
last_boost = 0;
|
|
last_kick = 0;
|
|
// cancel dash/boost
|
|
A_StopSound(CHAN_JETPACK);
|
|
fuelcooldown = 0.;
|
|
dashcooldown = 0.;
|
|
dashboost = 0.;
|
|
dashsnd = false;
|
|
// prevent sudden stomping if we were previously falling
|
|
lastvelz = vel.z;
|
|
// early cancel gestures
|
|
if ( player )
|
|
{
|
|
if ( player.ReadyWeapon is 'SWWMItemGesture' )
|
|
player.ReadyWeapon = SWWMItemGesture(player.ReadyWeapon).gest;
|
|
if ( player.ReadyWeapon is 'SWWMGesture' )
|
|
{
|
|
player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon;
|
|
player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect"));
|
|
}
|
|
}
|
|
// re-add ourselves to the "suckable list" (otherwise the Ynykron Singularity won't hurt us)
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.SuckableActors.Push(self);
|
|
// re-attach shadow
|
|
if ( swwm_shadows <= 0 ) return;
|
|
let ti = ThinkerIterator.Create("SWWMShadow");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( a.target == self )
|
|
return; // shadow already attached
|
|
}
|
|
SWWMShadow.Track(self);
|
|
}
|
|
override bool PreTeleport( Vector3 destpos, double destangle, int flags )
|
|
{
|
|
// store old pos
|
|
pretelepos = pos;
|
|
return true;
|
|
}
|
|
override void PostTeleport( Vector3 destpos, double destangle, int flags )
|
|
{
|
|
// subtract travel distance from the stats, and add it to a separate stat
|
|
double traveldist = level.Vec3Diff(pretelepos,pos).length();
|
|
if ( waterlevel < 2 )
|
|
{
|
|
if ( !player.onground || bNoGravity )
|
|
mystats.airdist -= traveldist;
|
|
else mystats.grounddist -= traveldist;
|
|
}
|
|
else mystats.swimdist -= traveldist;
|
|
mystats.teledist += traveldist;
|
|
// 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;
|
|
// notify carried lamp that we just moved
|
|
let l = SWWMLamp(FindInventory("SWWMLamp"));
|
|
if ( l && l.thelamp )
|
|
CompanionLamp(l.thelamp).justteleport = true;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
// normal idle
|
|
#### # 2;
|
|
XZW1 A 1
|
|
{
|
|
if ( !player || (player.mo != self) ) return ResolveState("VoodooSpawn");
|
|
return A_JumpIf(player&&(player.mo==self)&&(abs(player.cmd.yaw|player.cmd.pitch)>128),"Turn");
|
|
}
|
|
Wait;
|
|
See:
|
|
// normal walking
|
|
#### # 2;
|
|
XZW1 BCD 2 A_JumpIf(FastCheck(false),"SeeFast");
|
|
XZW1 E 0 A_Footstep(1);
|
|
XZW1 EFGHIJKL 2 A_JumpIf(FastCheck(false),"SeeFast");
|
|
XZW1 M 0 A_Footstep(-1);
|
|
XZW1 MNOPA 2 A_JumpIf(FastCheck(false),"SeeFast");
|
|
Goto See+1;
|
|
Turn:
|
|
#### # 8 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
|
XZW1 C 1 A_JumpIf(!player||!(player.cmd.yaw|player.cmd.pitch),1);
|
|
Wait;
|
|
XZW1 C 3 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
|
|
Goto Spawn+1;
|
|
SeeFast:
|
|
// sprinting
|
|
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
|
|
XZW1 QRST 2;
|
|
Goto SeeFastLoop;
|
|
SeeFastLoop:
|
|
// keep sprinting
|
|
XZW1 U 0 A_Footstep(1,true);
|
|
XZW1 UVWXYZ 2 A_JumpIf(FastCheck(true),"SeeFastEnd");
|
|
XZW2 A 2 A_JumpIf(FastCheck(true),"SeeFastEnd");
|
|
XZW2 B 0 A_Footstep(-1,true);
|
|
XZW2 BCDEFG 2 A_JumpIf(FastCheck(true),"SeeFastEnd");
|
|
XZW1 T 2 A_JumpIf(FastCheck(true),"SeeFastEnd");
|
|
Goto SeeFastLoop;
|
|
SeeFastEnd:
|
|
// brake
|
|
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
|
|
XZW2 HIJKL 2;
|
|
Goto Spawn+1;
|
|
Pain:
|
|
// ouchy
|
|
XZW1 A 1
|
|
{
|
|
if ( !player || (player.mo != self) ) return ResolveState("VoodooPain");
|
|
return A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchPain");
|
|
}
|
|
XZW2 M 1 A_DemoPain();
|
|
XZW2 NOPQ 1;
|
|
Goto Spawn+1;
|
|
Death:
|
|
XDeath:
|
|
// ded
|
|
XZW1 A 0
|
|
{
|
|
if ( !player || (player.mo != self) ) return ResolveState("VoodooDeath");
|
|
return A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchDeath");
|
|
}
|
|
XZW1 A 2
|
|
{
|
|
A_DemoScream();
|
|
A_NoBlocking();
|
|
}
|
|
XZW2 RSTUVWXYZ 2;
|
|
XZW3 ABCDEFG 2;
|
|
XZW3 H 1 A_DMFade();
|
|
Wait;
|
|
Jump:
|
|
// start boost
|
|
#### # 2;
|
|
XZW3 IJKLMNO 2
|
|
{
|
|
if ( player.onground||bNoGravity||(waterlevel>=3) )
|
|
return ResolveState("JumpEnd");
|
|
A_BoostUp(true);
|
|
return ResolveState(null);
|
|
}
|
|
// keep boost
|
|
XZW3 P 1
|
|
{
|
|
if ( player.onground||bNoGravity||(waterlevel>=3) )
|
|
return ResolveState("JumpEnd");
|
|
A_BoostUp(false);
|
|
return ResolveState(null);
|
|
}
|
|
XZW3 P 1 A_JumpIf((vel.z<-10)&&(pos.z>(floorz+80)),"Fall");
|
|
Goto Jump+8;
|
|
JumpEnd:
|
|
// stop boost
|
|
#### # 2;
|
|
XZW3 PQRSTUVW 2;
|
|
Goto Spawn+1;
|
|
Fall:
|
|
// start fall
|
|
#### # 4;
|
|
XZW3 XYZ 2 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd");
|
|
XZW4 AB 2 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd");
|
|
Goto FallLoop;
|
|
FallLoop:
|
|
// falling
|
|
XZW4 CDEFGH 3 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd");
|
|
Goto FallLoop;
|
|
FallEnd:
|
|
// landing
|
|
XZW4 CIJKLMN 2;
|
|
Goto Spawn+1;
|
|
Dash:
|
|
#### # 2;
|
|
XZW4 O 2 A_Dash();
|
|
XZW4 PQRS 2 A_Dash();
|
|
Goto Dash+2;
|
|
DashEnd:
|
|
XZW4 TUVWX 2;
|
|
Goto Spawn+1;
|
|
Wave:
|
|
#### # 3;
|
|
XZW4 YZ 3;
|
|
XZW5 ABCDEFGHIJKLM 3;
|
|
Goto Spawn+1;
|
|
Approve:
|
|
#### # 3;
|
|
XZW5 NOPQRSTUVWXYZ 3;
|
|
XZW6 ABCD 3;
|
|
Goto Spawn+1;
|
|
Victory:
|
|
#### # 3;
|
|
XZW6 EFGHIJKLMNOPQRSTUVW 3;
|
|
Goto Spawn+1;
|
|
BlowKiss:
|
|
#### # 3;
|
|
XZWD EFGHIJKLMNOPQRSTUVW 3;
|
|
Goto Spawn+1;
|
|
Headpat:
|
|
#### # 3;
|
|
XZWH ST 3;
|
|
XZWH UVW 2;
|
|
XZWH XYZ 1;
|
|
XZWI A 1;
|
|
XZWI BCDE 2;
|
|
XZWI FGH 1;
|
|
XZWH XYZ 1;
|
|
XZWI A 1;
|
|
XZWI BCDE 2;
|
|
XZWI FGH 1;
|
|
XZWI IJK 2;
|
|
XZWI LMNO 3;
|
|
Goto Spawn+1;
|
|
Ragepat:
|
|
#### # 3;
|
|
XZWH ST 2;
|
|
XZWH UVW 1;
|
|
XZWH XZ 1;
|
|
XZWI BCDE 1;
|
|
XZWI FH 1;
|
|
XZWH XZ 1;
|
|
XZWI BCDE 1;
|
|
XZWI FH 1;
|
|
XZWH XZ 1;
|
|
XZWI BCDE 1;
|
|
XZWI FH 1;
|
|
XZWH XZ 1;
|
|
XZWI BCDE 1;
|
|
XZWI FH 1;
|
|
XZWI IJK 1;
|
|
XZWI LMNO 2;
|
|
Goto Spawn+1;
|
|
Missile:
|
|
// attacking
|
|
#### # 2;
|
|
XZW6 XYZ 2;
|
|
XZW7 ABC 2;
|
|
Goto Spawn+1;
|
|
Melee:
|
|
// ponch
|
|
#### # 2;
|
|
XZW8 TUVWXYZ 2;
|
|
XZW9 ABCDEF 2;
|
|
Goto Spawn+1;
|
|
FastMelee:
|
|
// ponch (fast)
|
|
#### # 2;
|
|
XZW8 TUVWXYZ 1;
|
|
XZW9 ABCDE 1;
|
|
XZW9 F 2;
|
|
Goto Spawn+1;
|
|
Reload:
|
|
// reload
|
|
#### # 2;
|
|
XZW9 GHIJKLMNOPQRSTUVWXYZ 2;
|
|
XZWA ABCDE 2;
|
|
Goto Spawn+1;
|
|
CheckGun:
|
|
// speen
|
|
#### # 2;
|
|
XZWA FGHIJKLMNOPQRSTUVWXY 2;
|
|
Goto Spawn+1;
|
|
StartCrouch:
|
|
// go crouching
|
|
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
|
|
XZW7 DEFGH 1;
|
|
XZW7 IJKL 2;
|
|
Goto Crouch+1;
|
|
Crouch:
|
|
#### # 4;
|
|
XZW7 M -1;
|
|
Stop;
|
|
CrouchMove:
|
|
XZW7 MN 2;
|
|
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
|
XZW7 OPQRS 2;
|
|
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
|
XZW7 TUV 2;
|
|
Loop;
|
|
CrouchWave:
|
|
#### # 3;
|
|
XZWF ABCDEFGHIJKLMNO 3;
|
|
Goto Crouch+1;
|
|
CrouchApprove:
|
|
#### # 3;
|
|
XZWF PQRSTUVWXYZ 3;
|
|
XZWG ABCDEF 3;
|
|
Goto Crouch+1;
|
|
CrouchVictory:
|
|
#### # 3;
|
|
XZWG GHIJKLMNOPQRSTUVWXY 3;
|
|
Goto Crouch+1;
|
|
CrouchBlowKiss:
|
|
#### # 3;
|
|
XZWG Z 3;
|
|
XZWH ABCDEFGHIJKLMNOPQR 3;
|
|
Goto Crouch+1;
|
|
CrouchMissile:
|
|
XZW7 M 2;
|
|
XZW7 WXYZ 2;
|
|
XZW8 AB 2;
|
|
Goto Crouch+1;
|
|
CrouchMelee:
|
|
XZW7 M 2;
|
|
XZWA Z 2;
|
|
XZWB ABCDEFGHIJKL 2;
|
|
Goto Crouch+1;
|
|
CrouchFastMelee:
|
|
XZW7 M 2;
|
|
XZWA Z 1;
|
|
XZWB ABCDEFGHIJK 1;
|
|
XZWB L 2;
|
|
Goto Crouch+1;
|
|
CrouchReload:
|
|
XZW7 M 2;
|
|
XZWB MNOPQRSTUVWXYZ 2;
|
|
XZWC ABCDEFGHIJ 2;
|
|
Goto Crouch+1;
|
|
CrouchCheckGun:
|
|
XZW7 M 2;
|
|
XZWC LMNOPQRSTUVWXYZ 2;
|
|
XZWD ABCD 2;
|
|
Goto Crouch+1;
|
|
CrouchPain:
|
|
XZW7 M 1;
|
|
XZW8 C 1 A_DemoPain();
|
|
XZW8 DEF 1;
|
|
Goto Crouch+1;
|
|
CrouchDeath:
|
|
XZW7 M 2
|
|
{
|
|
A_DemoScream();
|
|
A_NoBlocking();
|
|
}
|
|
XZW8 GHIJK 2;
|
|
XZW8 L 1 A_DMFade();
|
|
Wait;
|
|
EndCrouch:
|
|
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
|
|
XZW8 MNOPQRS 2;
|
|
Goto Spawn+1;
|
|
Float:
|
|
#### # 3;
|
|
XZWD XYZ 3;
|
|
XZWE ABCDEFGHI 3;
|
|
Goto Float+1;
|
|
Swim:
|
|
#### # 2;
|
|
XZWE JKL 2;
|
|
Goto SwimLoop;
|
|
SwimLoop:
|
|
XZWE MNO 2;
|
|
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
|
XZWE PQRSTU 2;
|
|
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
|
|
XZWE VWX 2;
|
|
Loop;
|
|
SwimEnd:
|
|
#### # 2;
|
|
XZWE MYZ 2;
|
|
Goto Float+1;
|
|
VoodooSpawn:
|
|
XZWZ A -1;
|
|
Loop;
|
|
VoodooPain:
|
|
XZWZ A 1;
|
|
XZWZ B 1 A_DemoPain();
|
|
XZWZ CDEF 1;
|
|
Goto VoodooSpawn;
|
|
VoodooDeath:
|
|
XZWZ A 2
|
|
{
|
|
A_DemoScream();
|
|
A_NoBlocking();
|
|
}
|
|
XZWZ GHIJKLMNO 2;
|
|
XZWZ PQR 2;
|
|
XZWZ S -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class DashTrail : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 2;
|
|
Height 2;
|
|
Scale 0.3;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SetState(FindState("Spawn")+Random[ExploS](0,7));
|
|
let t = Spawn("DashTrail2",level.Vec3Offset(pos,vel*.3));
|
|
t.target = target;
|
|
t.vel = vel*1.2;
|
|
let s = Spawn("SWWMSmoke",level.Vec3Offset(pos,vel*1.6));
|
|
s.vel = vel*.8;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](64,128));
|
|
s.special1 = Random[ExploS](2,4);
|
|
s.scale *= 1.4;
|
|
s.alpha *= .3;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
// hack
|
|
if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
JFLB ABCDEFGH 1 Bright
|
|
{
|
|
A_FadeOut(.2);
|
|
A_SetScale(scale.x*.95);
|
|
}
|
|
Loop;
|
|
}
|
|
}
|
|
|
|
Class DashTrail2 : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 2;
|
|
Height 2;
|
|
Scale 0.2;
|
|
Alpha 0.4;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SetState(FindState("Spawn")+Random[ExploS](0,7));
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
// hack
|
|
if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
JFLR ABCDEFGH 1 Bright
|
|
{
|
|
A_FadeOut(.02);
|
|
A_SetScale(scale.x*1.04);
|
|
if ( waterlevel > 0 )
|
|
{
|
|
let b = Spawn("SWWMBubble",pos);
|
|
b.vel = vel;
|
|
b.scale *= scale.x;
|
|
Destroy();
|
|
}
|
|
}
|
|
Loop;
|
|
}
|
|
}
|
|
|
|
Class DemolitionistRadiusShockwaveTail : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 16;
|
|
Height 8;
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
pitch = min(85,(pitch+2)*1.05);
|
|
A_FadeOut(.02);
|
|
A_SetScale(scale.x*1.08,scale.y);
|
|
vel *= .98;
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class DemolitionistRadiusShockwave : Actor
|
|
{
|
|
Actor lasthit;
|
|
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Speed 15;
|
|
DamageFunction int(200*alpha);
|
|
DamageType "GroundPound";
|
|
Radius 16;
|
|
Height 8;
|
|
Alpha .4;
|
|
XScale .65;
|
|
YScale 3.;
|
|
PROJECTILE;
|
|
+DONTSPLASH;
|
|
+STEPMISSILE;
|
|
+NOEXPLODEFLOOR;
|
|
+FLATSPRITE;
|
|
+RIPPER;
|
|
+BLOODLESSIMPACT;
|
|
-NOGRAVITY;
|
|
}
|
|
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
|
|
{
|
|
if ( target == lasthit ) return 0;
|
|
lasthit = target;
|
|
if ( damage <= 0 ) return damage;
|
|
if ( (target.mass < LARGE_MASS) && !target.bDONTTHRUST )
|
|
{
|
|
target.vel.xy += vel.xy.unit()*(30000./max(50,target.mass))*alpha;
|
|
if ( (target.pos.z <= floorz) || !target.TestMobjZ() )
|
|
target.vel.z += (4000./max(50,target.mass))*alpha;
|
|
}
|
|
return damage;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
SetZ(floorz);
|
|
pitch = min(85,(pitch+2)*1.05);
|
|
if ( !Random[ExploS](0,3) )
|
|
Spawn("InvisibleSplasher",Vec3Offset(0,0,2));
|
|
let s = Spawn("DemolitionistRadiusShockwaveTail",pos);
|
|
s.vel = vel*.35;
|
|
s.scale = scale;
|
|
s.alpha = alpha*.4;
|
|
s.angle = angle;
|
|
s.pitch = pitch;
|
|
s.roll = roll;
|
|
A_FadeOut(.015);
|
|
A_SetScale(scale.x*1.08,scale.y);
|
|
vel *= .98;
|
|
}
|
|
Wait;
|
|
Death:
|
|
XZW1 A 1
|
|
{
|
|
SetZ(floorz);
|
|
A_FadeOut(.05);
|
|
A_SetScale(scale.x*1.1,scale.y*.97);
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class DemolitionistShockwave : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NOTELEPORT;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_QuakeEx(7,7,7,30,0,300+min(special1,50)*4,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:1.5);
|
|
if ( target.player != players[consoleplayer] )
|
|
{
|
|
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.3);
|
|
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.2,pitch:.7);
|
|
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.1,pitch:.4);
|
|
}
|
|
SWWMUtility.DoExplosion(self,40+min(special1,120),100000+min(special1*2000,150000),100+min(special1*2,130),80,DE_BLAST|DE_EXTRAZTHRUST,'GroundPound',target);
|
|
for ( int i=0; i<360; i+=5 )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
|
|
let s = Spawn("SWWMSmoke",Vec3Angle(4,i,8));
|
|
s.vel = pvel+(cos(i),sin(i),0)*7.;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](64,224));
|
|
s.special1 = Random[ExploS](1,4);
|
|
s.scale *= 1.5;
|
|
s.alpha *= .4;
|
|
}
|
|
if ( pos.z > floorz+16 ) return;
|
|
for ( int i=0; i<360; i+=5 )
|
|
{
|
|
let r = Spawn("DemolitionistRadiusShockwave",Vec3Angle(5,i));
|
|
r.target = target;
|
|
r.angle = i;
|
|
r.vel.xy = (cos(i),sin(i))*(r.speed+min(special1*.15,30));
|
|
r.alpha *= .1+min(special1*.03,.9);
|
|
}
|
|
int numpt = Random[ExploS](10,20);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
let raging = RagekitPower(target.FindInventory("RagekitPower"));
|
|
if ( raging || swwm_omnibust )
|
|
{
|
|
// bust the floor if we can
|
|
let tempme = new("LineTracer"); // gross hack to pass needed data
|
|
int dmg = 40+min(special1,120);
|
|
if ( raging ) dmg *= 8;
|
|
F3DFloor ff;
|
|
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = FloorSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) tempme.Results.ffloor = ff;
|
|
tempme.Results.HitSector = FloorSector;
|
|
tempme.Results.HitType = TRACE_HitFloor;
|
|
BusterWall.Bust(tempme.Results,dmg,target,(0,0,-1),pos.z);
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",pos);
|
|
ps.damagetype = 'GroundPound';
|
|
ps.target = target;
|
|
ps.special1 = dmg;
|
|
raging.DoHitFX();
|
|
}
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 140;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ReviveCooldown : Powerup
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "graphics/HUD/Icons/I_Revive.png";
|
|
Powerup.Duration -30;
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !Owner ) Destroy();
|
|
if ( Owner.Health <= 0 ) return; // timer does not go down when dead
|
|
if ( (EffectTics == 0) || ((EffectTics > 0) && (--EffectTics == 0)) )
|
|
Destroy ();
|
|
}
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
// adjust the duration
|
|
EffectTics = max(0,swwm_revivecooldown)*Thinker.TICRATE;
|
|
}
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_StartSound("demolitionist/revive",CHAN_ITEMEXTRA);
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_REFAIL"));
|
|
}
|
|
override void OwnerDied()
|
|
{
|
|
// do nothing, this "powerup" is preserved on death
|
|
}
|
|
}
|
|
|
|
// not an actual light, just handles the attach/detach
|
|
Class DemolitionistSelfLight : Actor
|
|
{
|
|
bool oldactive;
|
|
bool oldglow;
|
|
|
|
Default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
FloatBobPhase 0;
|
|
}
|
|
bool activelight()
|
|
{
|
|
// active all the time except when invisible or in certain
|
|
// animation frames
|
|
if ( target.bINVISIBLE || (target.alpha <= double.epsilon) ) return false;
|
|
if ( target.InStateSequence(target.CurState,target.FindState("Death")) && ((target.frame == 20) || (target.frame == 22) || (target.frame == 23) || (target.frame == 25) || (target.frame == 27) || (target.frame == 28) || (target.frame == 30) || (target.frame == 31) || (target.frame == 32) || (target.frame == 33) || (target.frame == 34)) )
|
|
return false;
|
|
if ( target.InStateSequence(target.CurState,target.FindState("CrouchDeath")) && ((target.frame == 7) || (target.frame == 10) || (target.frame == 11)) )
|
|
return false;
|
|
return true;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !(target is 'Demolitionist') || (Demolitionist(target).selflight != self) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
bool curactive = activelight();
|
|
if ( curactive && !oldactive )
|
|
target.A_AttachLight('DemoSelfLight',DynamicLight.PointLight,Color(56,72,88),200,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE|DynamicLight.LF_SPOT,(5,0,target.player?(target.player.viewz-target.pos.z):(target.height*.93)),0,30,90,target.pitch);
|
|
else if ( !curactive && oldactive )
|
|
target.A_RemoveLight('DemoSelfLight');
|
|
oldactive = curactive;
|
|
bool curglow = !(target.bINVISIBLE||(target.alpha <= double.epsilon));
|
|
if ( curglow && !oldglow ) target.A_AttachLight('DemoSelfLight2',DynamicLight.PointLight,Color(32,48,24),80,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE,(0,0,target.height/2));
|
|
else if ( !curglow && oldglow ) target.A_RemoveLight('DemoSelfLight2');
|
|
oldglow = curglow;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
Class LoveHeartTrail : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius .1;
|
|
Height 0.;
|
|
Alpha .1;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NOINTERACTION;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_FadeOut(.01);
|
|
scale *= .95;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
DOKI A -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class LoveHeartSparkle : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius .1;
|
|
Height 0.;
|
|
Scale .03;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NOINTERACTION;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Scale *= FRandom[ExploS](.75,1.5);
|
|
specialf1 = FRandom[ExploS](.95,.98);
|
|
specialf2 = FRandom[ExploS](.01,.03);
|
|
vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch))*FRandom[ExploS](2,8);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_SetScale(scale.x*specialf1);
|
|
A_FadeOut(specialf2);
|
|
Vector3 dir = vel;
|
|
double magvel = dir.length();
|
|
magvel *= .99;
|
|
if ( magvel > 0. )
|
|
{
|
|
dir /= magvel;
|
|
dir += .2*(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1));
|
|
vel = dir.unit()*magvel;
|
|
}
|
|
SetOrigin(level.Vec3Offset(pos,vel),true);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
DOKI A -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class LoveHeartBurstLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "LovePal";
|
|
ReactionTime 15;
|
|
Args 0,0,0,150;
|
|
}
|
|
}
|
|
|
|
Class LoveHeartLight : PointLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 255,176,208,80;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( target || target.InStateSequence(target.CurState,target.FindState("Death")) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
}
|
|
}
|
|
|
|
Class LoveHeart : Actor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_DOKIDOKI";
|
|
DamageType 'Love';
|
|
DamageFunction (clamp(special2,5,15));
|
|
Radius 4;
|
|
Height 4;
|
|
Speed 10;
|
|
Scale .2;
|
|
PROJECTILE;
|
|
+BLOODLESSIMPACT;
|
|
+FORCEXYBILLBOARD;
|
|
+SEEKERMISSILE;
|
|
+FOILINVUL;
|
|
+PAINLESS;
|
|
+NODAMAGETHRUST;
|
|
}
|
|
|
|
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
|
|
{
|
|
let raging = RagekitPower(self.target.FindInventory("RagekitPower"));
|
|
if ( raging )
|
|
{
|
|
bEXTREMEDEATH = true;
|
|
bNOEXTREMEDEATH = false;
|
|
}
|
|
else
|
|
{
|
|
bEXTREMEDEATH = false;
|
|
bNOEXTREMEDEATH = true;
|
|
}
|
|
if ( target is 'WolfensteinSS' ) target.bFRIENDLY = false;
|
|
if ( target.IsFriend(self.target) || (target is 'MBFHelperDog') )
|
|
{
|
|
int healamt = clamp(special2,5,15);
|
|
if ( raging )
|
|
{
|
|
healamt *= 8;
|
|
raging.DoHitFX();
|
|
}
|
|
if ( target.GiveBody(healamt,target.GetSpawnHealth()) )
|
|
{
|
|
SWWMScoreObj.Spawn(healamt,target.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+target.Height/2),Font.CR_BLUE);
|
|
SWWMHandler.DoFlash(target,Color(32,224,128,255),10);
|
|
}
|
|
if ( target is 'MBFHelperDog' )
|
|
{
|
|
// befriend good doggo
|
|
if ( target.bCOUNTKILL )
|
|
{
|
|
target.bCOUNTKILL = false;
|
|
level.total_monsters--;
|
|
}
|
|
target.bFRIENDLY = true;
|
|
if ( deathmatch )
|
|
target.SetFriendPlayer(self.target.player);
|
|
}
|
|
return 0;
|
|
}
|
|
Vector3 dirto = level.Vec3Diff(pos,target.Vec3Offset(0,0,target.Height/2)).unit();
|
|
SWWMUtility.DoKnockback(target,dirto,1500.*damage);
|
|
let bread = target.FindState("Pain");
|
|
if ( bread ) target.SetState(bread);
|
|
if ( raging )
|
|
{
|
|
damage *= 8;
|
|
raging.DoHitFX();
|
|
}
|
|
if ( target is 'WolfensteinSS' )
|
|
{
|
|
damage = int.max;
|
|
bEXTREMEDEATH = true;
|
|
bNOEXTREMEDEATH = false;
|
|
}
|
|
else if ( target is 'SWWMHangingKeen' )
|
|
damage = max(target.Health,damage); // rescued by love :3
|
|
else if ( SWWMHDoomHandler.IsCuteGirl(target) )
|
|
{
|
|
// no cute demon girl can resist demo's charm
|
|
damage = max(target.Health,damage);
|
|
bEXTREMEDEATH = false;
|
|
bNOEXTREMEDEATH = true;
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
override int SpecialMissileHit( Actor victim )
|
|
{
|
|
if ( !victim.bSHOOTABLE && (victim != tracer) ) return 1;
|
|
if ( tracer && (victim != tracer) ) return 1;
|
|
return -1;
|
|
}
|
|
|
|
action void A_HeartTick()
|
|
{
|
|
special1++;
|
|
if ( !(special1%3) && (special2 > 0) ) special2--;
|
|
A_SetScale(.2+.02*sin(special1*.25*TICRATE));
|
|
double magvel = vel.length();
|
|
if ( magvel > 0 )
|
|
{
|
|
Vector3 dir = vel/magvel;
|
|
vel = dir*min(30,magvel*1.1);
|
|
}
|
|
double steppy = vel.length()/4.;
|
|
for ( int i=2; i<6; i++ )
|
|
{
|
|
Vector3 dir2 = vel.unit();
|
|
let t = Spawn("LoveHeartTrail",level.Vec3Offset(pos,-dir2*steppy*i));
|
|
t.scale = scale;
|
|
}
|
|
int numpt = Random[ExploS](1,3);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let s = Spawn("LoveHeartSparkle",pos);
|
|
s.angle = FRandom[ExploS](0,360);
|
|
s.pitch = FRandom[ExploS](-90,90);
|
|
}
|
|
if ( !tracer || (tracer.Health <= 0) ) return;
|
|
double mag = vel.length();
|
|
vel = mag*(level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2)).unit()*mag*6./TICRATE+vel).unit();
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("misc/heart",CHAN_WEAPON);
|
|
//A_AttachLight('LOVELIGHT',DynamicLight.PointLight,Color(255,176,208),80,80,DYNAMICLIGHT.LF_ATTENUATE);
|
|
// can't use Attach/RemoveLight for now due to heavy performance issues
|
|
let l = Spawn("LoveHeartLight",pos);
|
|
l.target = self;
|
|
special2 = 25;
|
|
}
|
|
action void A_HeartBurst()
|
|
{
|
|
// use line
|
|
if ( BlockingLine )
|
|
{
|
|
int s = SWWMUtility.PointOnLineSide(pos.xy,BlockingLine);
|
|
int locknum = SWWMUtility.GetLineLock(BlockingLine);
|
|
if ( !locknum || (target && target.CheckKeys(locknum,false,true)) )
|
|
BlockingLine.RemoteActivate(target,s,SPAC_Use,pos);
|
|
}
|
|
if ( swwm_omnibust )
|
|
{
|
|
int dmg = GetMissileDamage(0,0);
|
|
let raging = RagekitPower(self.target.FindInventory("RagekitPower"));
|
|
if ( raging ) dmg *= 8;
|
|
if ( BusterWall.ProjectileBust(self,dmg,(cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch))) )
|
|
raging.DoHitFX();
|
|
}
|
|
A_SetRenderStyle(1.,STYLE_Add);
|
|
//A_RemoveLight('LOVELIGHT');
|
|
CheckSplash(40);
|
|
A_QuakeEx(2,2,2,8,0,300,"",QF_RELATIVE|QF_SCALEDOWN);
|
|
A_StartSound("bestsound",CHAN_VOICE);
|
|
Spawn("LoveHeartBurstLight",pos);
|
|
int numpt = Random[ExploS](10,15);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.5,4);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(10,8,9)*Random[ExploS](20,25));
|
|
s.special1 = Random[ExploS](1,3);
|
|
s.scale *= 2.;
|
|
s.alpha *= .6;
|
|
}
|
|
numpt = Random[ExploS](40,50);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let s = Spawn("LoveHeartSparkle",pos);
|
|
s.angle = FRandom[ExploS](0,360);
|
|
s.pitch = FRandom[ExploS](-90,90);
|
|
s.scale *= RandomPick[ExploS](1,3);
|
|
s.alpha *= 2;
|
|
}
|
|
}
|
|
action void A_HeartDie()
|
|
{
|
|
scale *= 1.2;
|
|
A_FadeOut();
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
DOKI A 1 Bright A_HeartTick();
|
|
Wait;
|
|
Death:
|
|
DOKI A 0 Bright A_HeartBurst();
|
|
DOKI A 1 Bright A_HeartDie();
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class HeadpatTracker : Actor
|
|
{
|
|
State patstate; // state the target will jump to on headpat
|
|
// if null, will simply resume current state
|
|
State deathstate; // if the actor enters this state, we can't headpat no more
|
|
int oldtargettics; // previous tics left in target's pre-pat state
|
|
bool patting; // currently in pat
|
|
Actor patter; // who's patting
|
|
double hdoomheightfix; // certain hdoom monsters kneel down, so their heads are lower than expected
|
|
double hdoomangfix; // fix for imp in a chair
|
|
bool lethalpat; // ending headpat immediately drops enemy health to 0
|
|
|
|
default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || (target.Health <= 0) || (deathstate && target.InStateSequence(target.CurState,deathstate)) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( patting )
|
|
{
|
|
// keep bolted in here
|
|
target.vel *= 0;
|
|
patter.vel *= 0;
|
|
patter.player.vel *= 0;
|
|
target.SetOrigin(pos,true);
|
|
// keep aim
|
|
double delta = deltaangle(target.angle,target.AngleTo(patter));
|
|
if ( abs(delta) < 1. ) target.angle = target.AngleTo(patter);
|
|
else target.angle += .3*delta;
|
|
delta = deltaangle(patter.angle,patter.AngleTo(target)+hdoomangfix);
|
|
if ( abs(delta) < 1. ) patter.A_SetAngle(patter.AngleTo(target)+hdoomangfix,SPF_INTERPOLATE);
|
|
else patter.A_SetAngle(patter.angle+.3*delta,SPF_INTERPOLATE);
|
|
double hfact = 1.2-hdoomheightfix;
|
|
delta = deltaangle(patter.pitch,SWWMUtility.PitchTo(patter,target,hfact));
|
|
if ( abs(delta) < 1. ) patter.A_SetPitch(SWWMUtility.PitchTo(patter,target,hfact),SPF_INTERPOLATE);
|
|
else patter.A_SetPitch(patter.pitch+.3*delta,SPF_INTERPOLATE);
|
|
return;
|
|
}
|
|
if ( (radius != target.radius+8) || (height != target.height+8) )
|
|
A_SetSize(target.radius+8,target.height+8);
|
|
if ( pos != target.pos ) SetOrigin(target.pos,false);
|
|
}
|
|
override bool Used( Actor user )
|
|
{
|
|
if ( !target ) return false;
|
|
if ( patting ) return false; // already on it
|
|
if ( user.player.crouchdir == -1 ) return false; // need to be standing up
|
|
if ( !user.player.onground ) return false; // need to be on solid ground
|
|
// check use range
|
|
Vector3 diff = level.Vec3Diff(user.Vec2OffsetZ(0,0,user.player.viewz),Vec3Offset(0,0,target.Height));
|
|
if ( abs(diff.z) > PlayerPawn(user.player.mo).UseRange ) return false;
|
|
if ( user is 'Demolitionist' )
|
|
{
|
|
patter = user;
|
|
let g = SWWMGesture.SetGesture(Demolitionist(patter),GS_Headpat);
|
|
if ( !g ) return false; // can't headpat at the moment
|
|
patting = true;
|
|
g.pats = self;
|
|
oldtargettics = target.tics;
|
|
target.tics = -1;
|
|
patter.player.cheats |= CF_TOTALLYFROZEN;
|
|
Demolitionist(patter).scriptedinvul = true;
|
|
target.bDORMANT = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Class HHitList
|
|
{
|
|
Actor a;
|
|
Vector3 dir;
|
|
}
|
|
|
|
enum EGestureSlot
|
|
{
|
|
// special use
|
|
GS_Headpat = -50,
|
|
GS_Grenade,
|
|
// no gesture
|
|
GS_Null = 0,
|
|
// general gestures
|
|
GS_Wave = 1,
|
|
GS_ThumbsUp,
|
|
GS_Victory,
|
|
GS_BlowKiss
|
|
};
|
|
|
|
// First person gestures
|
|
Class SWWMGesture : SWWMWeapon
|
|
{
|
|
Weapon formerweapon;
|
|
int whichgesture, nextgesture;
|
|
bool deaded, queued;
|
|
bool whichuse;
|
|
Class<SWWMItemGesture> whichweapon;
|
|
Array<bool> suse;
|
|
Array<Class<SWWMItemGesture> > sweapon;
|
|
int gonect;
|
|
HeadpatTracker pats; // for headpat gesture, our current tracker
|
|
|
|
// these should prevent autoswitch when out of ammo
|
|
override bool ReportHUDAmmo()
|
|
{
|
|
return false;
|
|
}
|
|
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
|
|
{
|
|
return false;
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
return false;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner || !Owner.player || (Owner.player.ReadyWeapon != self) )
|
|
return;
|
|
let psp = Owner.player.FindPSprite(PSP_WEAPON);
|
|
if ( !psp ) return;
|
|
if ( (Owner.Health <= 0) && (psp.CurState != ResolveState("Deselect")) )
|
|
Owner.player.SetPSprite(PSP_WEAPON,ResolveState("Deselect"));
|
|
}
|
|
|
|
static SWWMGesture SetGesture( PlayerPawn mo, int which )
|
|
{
|
|
if ( !mo || !(mo is 'Demolitionist') ) return null; // only Demo
|
|
if ( mo.Health <= 0 ) return null; // dead
|
|
if ( mo.player.cheats&CF_TOTALLYFROZEN ) return null; // frozen today
|
|
SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture"));
|
|
if ( w && ((mo.player.PendingWeapon is 'SWWMGesture') || (mo.player.ReadyWeapon is 'SWWMGesture')
|
|
|| (mo.player.PendingWeapon is 'SWWMItemGesture') || (mo.player.ReadyWeapon is 'SWWMItemGesture')) )
|
|
{
|
|
// already gesturing
|
|
// just queue another one
|
|
if ( which <= 0 ) return null; // these gestures can't be queued
|
|
else
|
|
{
|
|
w.nextgesture = which;
|
|
w.queued = true;
|
|
}
|
|
return null;
|
|
}
|
|
if ( !w )
|
|
{
|
|
w = SWWMGesture(Spawn("SWWMGesture"));
|
|
mo.AddInventory(w);
|
|
}
|
|
if ( mo.player.PendingWeapon != WP_NOCHANGE ) w.formerweapon = mo.player.PendingWeapon;
|
|
else w.formerweapon = mo.player.ReadyWeapon;
|
|
w.whichweapon = null;
|
|
w.whichgesture = which;
|
|
mo.player.PendingWeapon = w;
|
|
return w;
|
|
}
|
|
|
|
// "special" gestures are run by switching to another "weapon"
|
|
static SWWMGesture SetSpecialGesture( PlayerPawn mo, Class<SWWMItemGesture> a, bool used = false )
|
|
{
|
|
if ( !mo || !(mo is 'Demolitionist') ) return null; // only Demo
|
|
if ( mo.Health <= 0 ) return null; // dead
|
|
if ( mo.player.cheats&CF_TOTALLYFROZEN ) return null; // frozen today
|
|
if ( !a ) return null;
|
|
SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture"));
|
|
if ( w && ((mo.player.PendingWeapon is 'SWWMGesture') || (mo.player.ReadyWeapon is 'SWWMGesture')
|
|
|| (mo.player.PendingWeapon is 'SWWMItemGesture') || (mo.player.ReadyWeapon is 'SWWMItemGesture')) )
|
|
{
|
|
// already gesturing
|
|
// queue if unique
|
|
if ( (w.sweapon.Size() <= 0) || (w.sweapon.Find(a) != w.sweapon.Size()) )
|
|
{
|
|
w.sweapon.Push(a);
|
|
w.suse.Push(used);
|
|
}
|
|
return null;
|
|
}
|
|
if ( !w )
|
|
{
|
|
w = SWWMGesture(Spawn("SWWMGesture"));
|
|
mo.AddInventory(w);
|
|
}
|
|
if ( mo.player.PendingWeapon != WP_NOCHANGE ) w.formerweapon = mo.player.PendingWeapon;
|
|
else w.formerweapon = mo.player.ReadyWeapon;
|
|
w.whichgesture = GS_Null;
|
|
w.whichweapon = a;
|
|
w.whichuse = used;
|
|
mo.player.PendingWeapon = w;
|
|
return w;
|
|
}
|
|
|
|
action void A_CallPlayerGesture( statelabel st, statelabel cst )
|
|
{
|
|
if ( invoker.Owner.Health <= 0 ) return;
|
|
if ( (player.crouchdir == -1) && invoker.Owner.FindState(cst) )
|
|
invoker.Owner.SetStateLabel(cst);
|
|
else if ( invoker.Owner.FindState(st) )
|
|
invoker.Owner.SetStateLabel(st);
|
|
}
|
|
|
|
action void A_FinishGesture()
|
|
{
|
|
if ( invoker.sweapon.Size() > 0 )
|
|
{
|
|
invoker.whichgesture = GS_Null;
|
|
invoker.whichweapon = invoker.sweapon[0];
|
|
invoker.whichuse = invoker.suse[0];
|
|
// push back
|
|
invoker.sweapon.Delete(0);
|
|
invoker.suse.Delete(0);
|
|
player.SetPSprite(PSP_WEAPON,ResolveState("Ready"));
|
|
return;
|
|
}
|
|
if ( invoker.queued )
|
|
{
|
|
invoker.whichweapon = null;
|
|
invoker.whichgesture = invoker.nextgesture;
|
|
invoker.queued = false;
|
|
player.SetPSprite(PSP_WEAPON,ResolveState("Ready"));
|
|
return;
|
|
}
|
|
player.PendingWeapon = invoker.formerweapon;
|
|
player.SetPSprite(PSP_WEAPON,ResolveState("Deselect"));
|
|
}
|
|
|
|
action void A_Headpat()
|
|
{
|
|
A_StartSound("demolitionist/petting",CHAN_WEAPON,CHANF_OVERLAP,.4);
|
|
let pt = invoker.pats;
|
|
if ( !pt ) return;
|
|
int numpt = Random[ExploS](6,9);
|
|
Vector3 dir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
Vector3 patpos = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz-4),dir*30.);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let s = Spawn("LoveHeartSparkle",patpos);
|
|
s.angle = FRandom[ExploS](0,360);
|
|
s.pitch = FRandom[ExploS](-90,90);
|
|
}
|
|
if ( pt.target && pt.target.bSHOOTABLE )
|
|
{
|
|
int healamt = 10;
|
|
let raging = RagekitPower(FindInventory("RagekitPower"));
|
|
if ( raging )
|
|
{
|
|
healamt *= 8;
|
|
raging.DoHitFX();
|
|
}
|
|
if ( pt.target.GiveBody(healamt,pt.target.GetSpawnHealth()) )
|
|
{
|
|
SWWMScoreObj.Spawn(healamt,pt.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+pt.target.Height/2),Font.CR_BLUE);
|
|
SWWMHandler.DoFlash(pt.target,Color(32,224,128,255),10);
|
|
}
|
|
}
|
|
}
|
|
|
|
action void A_HeadpatEnd()
|
|
{
|
|
player.cheats &= ~CF_TOTALLYFROZEN;
|
|
Demolitionist(player.mo).scriptedinvul = false;
|
|
let pt = invoker.pats;
|
|
if ( !pt ) return;
|
|
pt.patting = false;
|
|
let t = pt.target;
|
|
if ( t )
|
|
{
|
|
if ( pt.patstate ) t.SetState(pt.patstate);
|
|
else t.tics = pt.oldtargettics;
|
|
t.bDORMANT = false;
|
|
if ( t.bISMONSTER )
|
|
{
|
|
if ( pt.lethalpat )
|
|
{
|
|
t.DamageMobj(invoker,self,t.Health,'Love',DMG_FORCED|DMG_THRUSTLESS);
|
|
pt.Destroy();
|
|
return;
|
|
}
|
|
// befriend
|
|
if ( t.bCOUNTKILL )
|
|
{
|
|
t.bCOUNTKILL = false;
|
|
level.total_monsters--;
|
|
}
|
|
if ( t.special && !(t.ActivationType&THINGSPEC_NoDeathSpecial) )
|
|
{
|
|
Actor whomst = level.actownspecial?t:self;
|
|
if ( t.ActivationType&THINGSPEC_ThingActs ) whomst = t;
|
|
else if ( t.ActivationType&THINGSPEC_TriggerActs ) whomst = self;
|
|
level.ExecuteSpecial(t.special,whomst,null,false,t.args[0],t.args[1],t.args[2],t.args[3],t.args[4]);
|
|
t.special = 0;
|
|
}
|
|
t.bFRIENDLY = true;
|
|
if ( deathmatch )
|
|
t.SetFriendPlayer(player);
|
|
// cancel any attacks
|
|
if ( t.InStateSequence(t.CurState,t.FindState("Missile"))
|
|
|| t.InStateSequence(t.CurState,t.FindState("Melee")) )
|
|
t.SetState(t.FindState("See"));
|
|
}
|
|
}
|
|
}
|
|
|
|
action void A_ThrowMag()
|
|
{
|
|
let weap = Weapon(invoker);
|
|
if ( !weap ) return;
|
|
Vector3 x, y, z, x2, y2, z2;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-2*y-3*z);
|
|
double a = FRandom[Spread](0,360), s = FRandom[Spread](0,.005);
|
|
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
|
|
Vector3 dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
let p = Spawn("ExplodiumMagProj",origin);
|
|
p.special1 = 7;
|
|
p.target = self;
|
|
p.angle = atan2(dir.y,dir.x);
|
|
p.pitch = asin(-dir.z);
|
|
p.vel = dir*p.speed;
|
|
p.vel.z += 5.;
|
|
p.vel += vel*.5;
|
|
}
|
|
|
|
action void A_Smooch()
|
|
{
|
|
if ( (player == players[consoleplayer]) && (CVar.GetCVar('swwm_mutevoice',player).GetInt() < 4) )
|
|
A_StartSound("demolitionist/smooch",CHAN_DEMOVOICE,CHANF_OVERLAP,.4);
|
|
}
|
|
|
|
action void A_BlowKiss()
|
|
{
|
|
if ( (player == players[consoleplayer]) && (CVar.GetCVar('swwm_mutevoice',player).GetInt() < 4) )
|
|
A_StartSound("demolitionist/blowkiss",CHAN_DEMOVOICE,CHANF_OVERLAP,.4);
|
|
let weap = Weapon(invoker);
|
|
if ( !weap ) return;
|
|
Vector3 x, y, z, x2, y2, z2, dir;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-1*z);
|
|
let p = Spawn("LoveHeart",origin);
|
|
p.target = self;
|
|
p.angle = angle;
|
|
p.pitch = BulletSlope();
|
|
p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
|
|
// try to catch target in cone of vision
|
|
[x2, y2, z2] = swwm_CoordUtil.GetAxes(p.pitch,p.angle,0);
|
|
Array<HHitList> hits;
|
|
hits.Clear();
|
|
int rings = 1;
|
|
FLineTraceData d;
|
|
for ( double i=0; i<.2; i+=.02 )
|
|
{
|
|
for ( int j=0; j<360; j+=(360/rings) )
|
|
{
|
|
dir = (x2+y2*cos(j)*i+z2*sin(j)*i).unit();
|
|
LineTrace(atan2(dir.y,dir.x),8000.,asin(-dir.z),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
|
|
if ( d.HitType != TRACE_HitActor ) continue;
|
|
bool addme = true;
|
|
for ( int k=0; k<hits.Size(); k++ )
|
|
{
|
|
if ( hits[k].a != d.HitActor ) continue;
|
|
if ( (hits[k].dir dot x2) < (dir dot x2) )
|
|
hits[k].dir = dir; // closer to centerpoint
|
|
addme = false;
|
|
break;
|
|
}
|
|
if ( !addme ) continue;
|
|
let nhit = new("HHitList");
|
|
nhit.a = d.HitActor;
|
|
nhit.dir = dir;
|
|
hits.Push(nhit);
|
|
}
|
|
rings += 5;
|
|
}
|
|
int closest = -1;
|
|
double closestdot = -1;
|
|
for ( int i=0; i<hits.Size(); i++ )
|
|
{
|
|
double thisdot = (hits[i].dir dot x2);
|
|
if ( thisdot < closestdot ) continue;
|
|
closest = i;
|
|
closestdot = thisdot;
|
|
}
|
|
if ( closest != -1 ) p.tracer = hits[closest].a;
|
|
}
|
|
|
|
Default
|
|
{
|
|
+WEAPON.CHEATNOTWEAPON;
|
|
+WEAPON.NO_AUTO_SWITCH;
|
|
+WEAPON.WIMPY_WEAPON;
|
|
+SWWMWEAPON.HIDEINMENU;
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.UNTOSSABLE;
|
|
+INVENTORY.UNCLEARABLE;
|
|
Weapon.SelectionOrder int.max;
|
|
}
|
|
States
|
|
{
|
|
Select:
|
|
XZW1 A 1 A_FullRaise();
|
|
Goto Ready;
|
|
Ready:
|
|
XZW1 A 1
|
|
{
|
|
if ( invoker.whichweapon )
|
|
{
|
|
SWWMItemGesture g = SWWMItemGesture(FindInventory(invoker.whichweapon));
|
|
if ( !g )
|
|
{
|
|
g = SWWMItemGesture(Spawn(invoker.whichweapon));
|
|
AddInventory(g);
|
|
}
|
|
g.gest = invoker;
|
|
player.ReadyWeapon = g;
|
|
if ( invoker.whichuse ) player.SetPSPrite(PSP_WEAPON,g.FindState("AltFire"));
|
|
else player.SetPSPrite(PSP_WEAPON,g.FindState("Fire"));
|
|
return ResolveState(null);
|
|
}
|
|
switch ( invoker.whichgesture )
|
|
{
|
|
case GS_Headpat:
|
|
return ResolveState("Headpat");
|
|
case GS_Grenade:
|
|
return ResolveState("QuickGrenade");
|
|
case GS_Wave:
|
|
return ResolveState("Wave");
|
|
case GS_ThumbsUp:
|
|
return ResolveState("Approve");
|
|
case GS_Victory:
|
|
return ResolveState("Victory");
|
|
case GS_BlowKiss:
|
|
return ResolveState("BlowKiss");
|
|
}
|
|
return ResolveState("NoGesture");
|
|
}
|
|
Wait;
|
|
Fire:
|
|
XZW1 A 1;
|
|
Goto Ready;
|
|
Headpat:
|
|
XZW1 A 0 A_JumpIf(CountInv("RagekitPower"),"Ragepat");
|
|
XZW1 A 3 A_CallPlayerGesture("Headpat","Headpat");
|
|
XZW3 TU 3;
|
|
XZW3 V 2 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW3 WX 2;
|
|
XZW3 YZ 1;
|
|
XZW4 AB 1;
|
|
XZW4 C 2;
|
|
XZW4 D 2 A_Headpat();
|
|
XZW4 EF 2;
|
|
XZW4 GHI 1;
|
|
XZW3 YZ 1;
|
|
XZW4 AB 1;
|
|
XZW4 C 2;
|
|
XZW4 D 2 A_Headpat();
|
|
XZW4 EF 2;
|
|
XZW4 GHI 1;
|
|
XZW4 JK 2;
|
|
XZW4 L 2 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW4 MNOP 3;
|
|
XZW1 A 0 A_HeadpatEnd();
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
Ragepat:
|
|
XZW1 A 3 A_CallPlayerGesture("Ragepat","Ragepat");
|
|
XZW3 TU 2;
|
|
XZW3 V 1 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW3 WX 1;
|
|
XZW3 Y 1;
|
|
XZW4 AC 1;
|
|
XZW4 D 1 A_Headpat();
|
|
XZW4 EF 1;
|
|
XZW4 GI 1;
|
|
XZW3 Y 1;
|
|
XZW4 AC 1;
|
|
XZW4 D 1 A_Headpat();
|
|
XZW4 EF 1;
|
|
XZW4 GI 1;
|
|
XZW3 Y 1;
|
|
XZW4 AC 1;
|
|
XZW4 D 1 A_Headpat();
|
|
XZW4 EF 1;
|
|
XZW4 GI 1;
|
|
XZW3 Y 1;
|
|
XZW4 AC 1;
|
|
XZW4 D 1 A_Headpat();
|
|
XZW4 EF 1;
|
|
XZW4 GIJK 1;
|
|
XZW4 L 1 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW4 MNOP 2;
|
|
XZW1 A 0 A_HeadpatEnd();
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
QuickGrenade:
|
|
XZW4 Q 3;
|
|
XZW4 R 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW4 ST 3;
|
|
XZW4 U 3 A_PlayerReload();
|
|
XZW4 V 2 A_StartSound("explodium/magpin",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW4 WX 2;
|
|
XZW4 Y 3;
|
|
XZW4 Z 2
|
|
{
|
|
A_StartSound("explodium/throwmag",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_PlayerMelee();
|
|
}
|
|
XZW5 AB 2;
|
|
XZW5 CDEF 2;
|
|
XZW5 G 2 A_ThrowMag();
|
|
XZW5 HIJ 3;
|
|
XZW5 K 4;
|
|
XZW4 Q 0 A_JumpIf(player.cmd.buttons&BT_USER4,"QuickGrenade");
|
|
XZW4 Q -1 A_FinishGesture();
|
|
Stop;
|
|
Wave:
|
|
XZW1 A 3 A_CallPlayerGesture("Wave","CrouchWave");
|
|
XZW1 B 3;
|
|
XZW1 C 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW1 DEFGHIJ 3;
|
|
XZW1 K 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW1 LMNOP 3;
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
Approve:
|
|
XZW1 A 3 A_CallPlayerGesture("Approve","CrouchApprove");
|
|
XZW1 QR 3;
|
|
XZW1 S 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW1 TUVWXY 3;
|
|
XZW1 Z 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW2 A 3;
|
|
XZW2 BCDEFG 3;
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
Victory:
|
|
XZW1 A 3 A_CallPlayerGesture("Victory","CrouchVictory");
|
|
XZW2 HI 3;
|
|
XZW2 J 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW2 KLMNOPQR 3;
|
|
XZW2 S 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW2 TUVWXYZ 3;
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
BlowKiss:
|
|
XZW1 A 3 A_CallPlayerGesture("BlowKiss","CrouchBlowKiss");
|
|
XZW3 A 3;
|
|
XZW3 B 3;
|
|
XZW3 C 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW3 DE 3;
|
|
XZW3 F 3 A_Smooch();
|
|
XZW3 GHIJ 3;
|
|
XZW3 K 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW3 L 3 A_BlowKiss();
|
|
XZW3 MNOPQRS 3;
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
NoGesture:
|
|
XZW1 A -1 A_FinishGesture();
|
|
Stop;
|
|
Deselect:
|
|
XZW1 A -1 A_FullLower();
|
|
Stop;
|
|
}
|
|
}
|