Restore weapon precaching. Add precaching for gesture items. Fix broken physics of light actors. Add light actor physics to confetti. Fix composite items not keeping their tracers on travel. Fix ground distance reporting meters when it should be showing kilometers. Reattach player shadow on travel. Fix tip 37 erroneously listing slots from 6 and up as FOILINVUL when it should be 7 and up.
3582 lines
104 KiB
Text
3582 lines
104 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;
|
|
// 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));
|
|
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') && 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;
|
|
}
|
|
}
|
|
}
|
|
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 && (player.ReadyWeapon is 'SWWMGesture') )
|
|
{
|
|
player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon;
|
|
player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect"));
|
|
}
|
|
// 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');
|
|
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;
|
|
}
|
|
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;
|
|
State whichstate;
|
|
Actor whichcaller;
|
|
Array<State> sstate;
|
|
Array<Actor> scaller;
|
|
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 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 ( (mo.player.PendingWeapon == w) || (mo.player.ReadyWeapon == w) )
|
|
{
|
|
// 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.whichstate = null;
|
|
w.whichgesture = which;
|
|
mo.player.PendingWeapon = w;
|
|
return w;
|
|
}
|
|
|
|
// "special" gestures go to a specified state, which may be external
|
|
static SWWMGesture SetSpecialGesture( PlayerPawn mo, Actor a, bool pickup = 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
|
|
State which = null;
|
|
if ( pickup ) which = a.FindState("PickupGesture");
|
|
else which = a.FindState("UseGesture");
|
|
if ( !which ) return null; // state not found
|
|
SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture"));
|
|
if ( (mo.player.PendingWeapon == w) || (mo.player.ReadyWeapon == w) )
|
|
{
|
|
// already gesturing
|
|
// queue if unique
|
|
if ( w.sstate.Find(which) != w.sstate.Size() )
|
|
{
|
|
w.sstate.Push(which);
|
|
w.scaller.Push(a);
|
|
}
|
|
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.whichstate = which;
|
|
w.whichcaller = a;
|
|
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.sstate.Size() > 0 )
|
|
{
|
|
invoker.whichgesture = GS_Null;
|
|
invoker.whichstate = invoker.sstate[0];
|
|
invoker.whichcaller = invoker.scaller[0];
|
|
// push back
|
|
invoker.sstate.Delete(0);
|
|
invoker.scaller.Delete(0);
|
|
player.SetPSprite(PSP_WEAPON,ResolveState("Ready"));
|
|
return;
|
|
}
|
|
if ( invoker.queued )
|
|
{
|
|
invoker.whichstate = 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.whichstate )
|
|
{
|
|
// set caller (gross hack)
|
|
let psp = player.FindPSPrite(PSP_WEAPON);
|
|
psp.caller = invoker.whichcaller;
|
|
return invoker.whichstate;
|
|
}
|
|
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;
|
|
}
|
|
}
|