swwmgz_m/zscript/swwm_player.zsc
Marisa Kirisame 8b7656cef3 Weredragons and Ettins are pettable now.
Befriended monsters activate their death specials, if they have any.
Add colored icons to monster tags to denote bosses and friends.
Minigames will be part of a second free DLC update.
2020-11-07 13:54:21 +01:00

3476 lines
101 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;
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.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]));
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(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(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 ) // 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',players[consoleplayer]);
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,-1);
}
override void Tick()
{
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();
}
if ( player.mo != self ) return; // no voodoo dolls past this point
// 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(prev,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(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 ) 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(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(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) && BlockingLine )
{
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(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 )
{
// 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),false);
if ( floorz < storepos.z ) continue;
crossgap = true;
break;
}
SetOrigin(storepos,false);
floorz = storefloorz;
if ( !crossgap )
{
ssup = max(0,(pos.z-floorz));
SetOrigin(Vec2OffsetZ(0,0,floorz),true);
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(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;
players[i].mo.GiveInventory(item.GetClass(),1);
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("SmolInvisibleSplasher",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"));
}
}
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;
}
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;
}
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;
}
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,(12,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),-2);
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;
}
// First person gestures
Class SWWMGesture : SWWMWeapon
{
Weapon formerweapon;
int whichgesture, nextgesture;
bool deaded, queued;
Array<int> squeue;
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;
}
static SWWMGesture SetGesture( PlayerPawn mo, int which = 0 )
{
if ( !mo || !(mo is 'Demolitionist') ) return null; // only Demo
if ( mo.Health <= 0 ) return null; // dead
if ( mo.player.cheats&CF_TOTALLYFROZEN ) return null; // frozen today
if ( mo.player.PendingWeapon is 'SWWMGesture' ) return null; // already initiating a gesture
if ( mo.player.ReadyWeapon is 'SWWMGesture' )
{
// already gesturing
// just queue another one
let g = SWWMGesture(mo.player.ReadyWeapon);
if ( which <= -100 ) g.squeue.Push(which); // collectable gestures have a dedicated array
if ( which <= -1 ) return null; // special gestures can't be queued
else
{
g.nextgesture = which;
g.queued = true;
}
return null;
}
SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture"));
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 = which;
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.squeue.Size() > 0 )
{
invoker.whichgesture = invoker.squeue[0];
invoker.squeue.Delete(0); // push back
player.SetPSprite(PSP_WEAPON,ResolveState("Ready"));
return;
}
if ( invoker.queued )
{
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:
TNT1 A 1
{
A_FullRaise();
if ( Health <= 0 ) A_FinishGesture();
}
Goto Ready;
Ready:
TNT1 A 1
{
switch ( invoker.whichgesture )
{
case -2:
return ResolveState("Headpat");
case -1:
return ResolveState("QuickGrenade");
case 1:
return ResolveState("Approve");
case 2:
return ResolveState("Victory");
case 3:
return ResolveState("BlowKiss");
}
return ResolveState("Wave");
}
Wait;
Fire:
TNT1 A 1;
Goto Ready;
Headpat:
TNT1 A 0 A_JumpIf(CountInv("RagekitPower"),"Ragepat");
TNT1 A 3 A_CallPlayerGesture("Headpat","Headpat");
XZW4 NO 3;
XZW4 P 2 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 QR 2;
XZW4 STUV 1;
XZW4 W 2;
XZW4 X 2 A_Headpat();
XZW4 YZ 2;
XZW5 ABC 1;
XZW4 STUV 1;
XZW4 W 2;
XZW4 X 2 A_Headpat();
XZW4 YZ 2;
XZW5 ABC 1;
XZW5 DE 2;
XZW5 F 2 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW5 GHIJ 3;
TNT1 A 4 A_HeadpatEnd();
TNT1 A -1 A_FinishGesture();
Ragepat:
TNT1 A 3 A_CallPlayerGesture("Ragepat","Ragepat");
XZW4 NO 2;
XZW4 P 1 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 QR 1;
XZW4 SU 1;
XZW4 W 1;
XZW4 X 1 A_Headpat();
XZW4 YZ 1;
XZW5 AC 1;
XZW4 SU 1;
XZW4 W 1;
XZW4 X 1 A_Headpat();
XZW4 YZ 1;
XZW5 AC 1;
XZW4 SU 1;
XZW4 W 1;
XZW4 X 1 A_Headpat();
XZW4 YZ 1;
XZW5 AC 1;
XZW4 SU 1;
XZW4 W 1;
XZW4 X 1 A_Headpat();
XZW4 YZ 1;
XZW5 AC 1;
XZW5 DE 1;
XZW5 F 1 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW5 GHIJ 2;
TNT1 A 4 A_HeadpatEnd();
TNT1 A -1 A_FinishGesture();
QuickGrenade:
TNT1 A 3;
XZW3 S 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 TU 3;
XZW3 V 3 A_PlayerReload();
XZW3 W 2 A_StartSound("explodium/magpin",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 XY 2;
XZW3 Z 3;
XZW4 A 2
{
A_StartSound("explodium/throwmag",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW4 BC 2;
XZW4 DEFGH 2;
XZW4 I 2 A_ThrowMag();
XZW4 JKLM 3;
TNT1 A 4;
TNT1 A 0 A_JumpIf(player.cmd.buttons&BT_USER4,"QuickGrenade");
TNT1 A -1 A_FinishGesture();
Stop;
Wave:
TNT1 A 3 A_CallPlayerGesture("Wave","CrouchWave");
XZW1 AB 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 LMNO 3;
TNT1 A -1 A_FinishGesture();
Stop;
Approve:
TNT1 A 3 A_CallPlayerGesture("Approve","CrouchApprove");
XZW1 PQ 3;
XZW1 R 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW1 STUVWX 3;
XZW1 Y 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW1 Z 3;
XZW2 ABCDEF 3;
TNT1 A -1 A_FinishGesture();
Stop;
Victory:
TNT1 A 3 A_CallPlayerGesture("Victory","CrouchVictory");
XZW2 GH 3;
XZW2 I 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 JKLMNOPQ 3;
XZW2 R 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 STUVWXY 3;
TNT1 A -1 A_FinishGesture();
Stop;
BlowKiss:
TNT1 A 3 A_CallPlayerGesture("BlowKiss","CrouchBlowKiss");
XZW2 Z 3;
XZW3 A 3;
XZW3 B 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 CD 3;
XZW3 E 3 A_Smooch();
XZW3 FGHI 3;
XZW3 J 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 K 3 A_BlowKiss();
XZW3 LMNOPQR 3;
TNT1 A -1 A_FinishGesture();
Stop;
Deselect:
TNT1 A -1 A_FullLower();
Stop;
}
}