swwmgz_m/zscript/swwm_player.zsc
Marisa Kirisame 6068047fe8 Fix typo in SUSAN lore entry.
Restore weapon precaching.
Add precaching for gesture items.
Fix broken physics of light actors.
Add light actor physics to confetti.
Fix composite items not keeping their tracers on travel.
Fix ground distance reporting meters when it should be showing kilometers.
Reattach player shadow on travel.
Fix tip 37 erroneously listing slots from 6 and up as FOILINVUL when it should be 7 and up.
2020-12-14 21:03:08 +01:00

3582 lines
104 KiB
Text

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