swwmgz_m/zscript/swwm_player.zsc
Marisa Kirisame 65db7e8367 0.9.8b release:
- Crouched gesture animations.
 - Proper crouch-jumping (now always enabled).
 - New fanart by Endie.
 - Swimming animations (also used by flight).
 - Hexen-style startup screen.
 - More model cleanup.
 - Prevent Wallbuster reload menu from opening on intermissions.
 - Intermissions now only handle fire and use for advance, to prevent some lil' accidents.
 - Holding altfire on intermissions hides ui elements, so the bg is fully visible.
 - Begin writing lore for collectibles (these will come in a couple updates).
 - Fix fuzz shader being affected by texture upscaling.
 - Enemies with >=1000 starting hp also can drop golden shells.
 - Explodium Gun no longer shows with a "1x" prefix on menus when single.
 - Player animation transition tweaks.
2020-09-24 14:05:04 +02:00

3044 lines
89 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;
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;
Property DashFuel : dashfuel;
Default
{
Speed 1;
Radius 16;
Height 56;
Mass 500;
PainChance 255;
Player.DisplayName "Demolitionist";
Player.StartItem "ExplodiumGun";
Player.StartItem "DeepImpact";
Player.ViewHeight 52;
Player.AirCapacity 0;
Player.GruntSpeed 20;
Player.SoundClass "demolitionist";
DamageFactor "Drowning", 0.0;
DamageFactor "Poison", 0.0;
DamageFactor "PoisonCloud", 0.0;
DamageFactor "Falling", 0.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 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 item = Inventory(Spawn(type));
item.ClearCounters(); // don't increase item counts
item.Amount = item.MaxAmount;
if ( !item.CallTryPickup(self) ) item.Destroy();
}
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "keys") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
if ( !(AllActorClasses[i] is "Key") ) continue;
// clean up redundant keys
if ( !(gameinfo.gametype&GAME_Chex) && ((AllActorClasses[i] is 'ChexRedCard') || (AllActorClasses[i] is 'ChexBlueCard') || (AllActorClasses[i] is 'ChexYellowCard')) ) continue;
else if ( (gameinfo.gametype&GAME_Chex) && !(AllActorClasses[i] is 'ChexRedCard') && !(AllActorClasses[i] is 'ChexBlueCard') && !(AllActorClasses[i] is 'ChexYellowCard') ) continue;
// don't give heretic keys in doom
if ( (gameinfo.gametype&GAME_DoomChex) && ((AllActorClasses[i] is 'KeyYellow') || (AllActorClasses[i] is 'KeyGreen') || (AllActorClasses[i] is 'KeyBlue')) ) continue;
let keyitem = GetDefaultByType(AllActorClasses[i]);
if ( keyitem.special1 )
{
let item = Inventory(Spawn(AllActorClasses[i]));
SWWMHandler.KeyTagFix(item);
if ( !item.CallTryPickup(self) ) item.Destroy();
}
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "weapons") )
{
let savedpending = player.PendingWeapon;
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Weapon>)(AllActorClasses[i]);
if ( !type || (type == "Weapon") ) continue;
// Don't give already owned weapons
let owned = FindInventory(type);
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
// Don't give replaced weapons unless the replacement was done by Dehacked.
let rep = GetReplacement(type);
if ( (rep == type) || (rep is "DehackedPickup") )
{
// Give the weapon only if it is set in a weapon slot.
if ( !player.weapons.LocateWeapon(type) ) continue;
readonly<Weapon> def = GetDefaultByType(type);
if ( (giveall == ALL_YESYES) || !def.bCheatNotWeapon )
GiveInventory(type,1,true);
}
}
player.PendingWeapon = savedpending;
if ( !giveall ) return;
}
if ( giveall || (name ~== "artifacts") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Inventory>)(AllActorClasses[i]);
if ( !type ) continue;
if ( !(gameinfo.gametype&(GAME_Hexen|GAME_Strife)) && (type is 'AmmoFabricator') ) continue; // no fabricators before hexen
// Don't give maxed items
let owned = FindInventory(type);
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
let def = GetDefaultByType (type);
if ( def.Icon.isValid() &&
!(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") && !(type is "Armor") && !(type is "Key") )
{
// Do not give replaced items unless using "give everything"
if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
}
if ( !giveall ) return;
}
if ( giveall || (name ~== "puzzlepieces") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<PuzzleItem>)(AllActorClasses[i]);
if ( !type ) continue;
let def = GetDefaultByType(type);
if ( !def.Icon.isValid() ) continue;
// Do not give replaced items unless using "give everything"
if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
if ( !giveall ) return;
}
if ( giveall ) 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();
// reset score (optional)
if ( swwm_resetscore )
{
let creds = SWWMCredits.Find(player);
if ( creds ) creds.credits = creds.hcredits = 0;
}
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
mystats = SWWMStats.Find(player);
lastground = true;
}
void A_Dash()
{
vel += dashdir*dashboost;
player.vel = vel.xy;
if ( dashboost < .2 ) dashboost = 0.;
else
{
if ( swwm_extraalert ) A_AlertMonsters(800);
dashboost *= (player.cmd.buttons&BT_USER2)?.9:.1;
}
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
dashfuel = max(0.,dashfuel-dashboost);
dashcooldown = min(40,max(10,int(dashcooldown*1.4)));
fuelcooldown = max(30,fuelcooldown);
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
SetStateLabel("DashEnd");
}
void A_BoostUp( bool initial = false )
{
vel += (0,0,1)*dashboost;
player.vel = vel.xy;
if ( dashboost < .2 ) dashboost = 0.;
else
{
if ( swwm_extraalert ) A_AlertMonsters(800);
dashboost *= (player.cmd.buttons&BT_JUMP)?.95:.4;
last_boost = level.maptime+1;
}
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
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) ) continue;
SWWMItemSense.Spawn(self,i);
}
}
void CheckUnderwaterAmb( bool restore = false )
{
Vector3 headpos = Vec3Offset(0,0,player.viewheight);
Vector3 centerpos = Vec3Offset(0,0,height/2);
Sector headregion = null;
if ( CurSector.moreflags&Sector.SECMF_UNDERWATER ) // check underwater sector
headregion = CurSector;
else if ( CurSector.heightsec ) // check height transfer
{
let hsec = CurSector.heightsec;
double fh = hsec.floorplane.ZAtPoint(pos.xy);
if ( pos.z < fh )
{
if ( headpos.z <= fh )
headregion = hsec;
}
else if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY) && (headpos.z > hsec.ceilingplane.ZAtPoint(pos.xy)) )
headregion = hsec;
}
else // check 3D floors
{
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
let ff = CurSector.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || (!(ff.flags&F3DFloor.FF_SWIMMABLE) && (ff.alpha == 0)) ) continue;
double ff_bottom = ff.bottom.ZAtPoint(pos.xy);
double ff_top = ff.top.ZAtPoint(pos.xy);
if ( (ff_top <= pos.z) || (ff_bottom > centerpos.z) ) continue;
if ( headpos.z <= ff_top )
{
headregion = ff.model;
}
break;
}
}
int curunder = UNDER_NONE;
if ( headregion )
{
switch ( headregion.damagetype )
{
case 'Fire':
case 'Lava':
curunder = UNDER_LAVA;
break;
case 'Slime':
case 'Poison':
case 'PoisonCloud':
curunder = UNDER_SLIME;
break;
case 'Ice':
case 'Drowning':
default:
curunder = UNDER_WATER;
break;
}
}
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_LOOPING|CHANF_UI);
if ( !restore && (players[consoleplayer].Camera == self) )
A_StartSound(entersnd[curunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
}
if ( !restore && (lastunder > UNDER_NONE) && (players[consoleplayer].Camera == self) )
A_StartSound(exitsnd[lastunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
}
if ( curunder > UNDER_NONE )
A_SoundVolume(CHAN_AMBEXTRA,(players[consoleplayer].Camera==self)?1.:0.);
lastunder = curunder;
}
override Vector2 BobWeapon( double ticfrac )
{
// non-mod weapons bob normally
if ( !(player.ReadyWeapon is 'SWWMWeapon') )
return Super.BobWeapon(ticfrac);
bool oldbob = !!(player.WeaponState&WF_WEAPONBOBBING);
player.WeaponState |= WF_WEAPONBOBBING; // always bob
Vector2 cur = Super.BobWeapon(ticfrac);
if ( !oldbob ) player.WeaponState &= ~WF_WEAPONBOBBING;
double fangle = oldangle*(1.-ticfrac)+angle*ticfrac;
double fpitch = (oldpitch*(1.-ticfrac)+pitch*ticfrac);
double flagangle = (oldlagangle*(1.-ticfrac)+lagangle*ticfrac);
double flagpitch = (oldlagpitch*(1.-ticfrac)+lagpitch*ticfrac);
double diffang = fangle-flagangle;
double diffpitch = fpitch-flagpitch;
if ( abs(diffang) > 1. )
{
int sgn = (diffang>0)?1:-1;
diffang = abs(diffang)**.7*sgn;
}
if ( abs(diffpitch) > 1. )
{
int sgn = (diffpitch>0)?1:-1;
diffpitch = abs(diffpitch)**.7*sgn;
}
Vector3 flagvel = oldlagvel*(1.-ticfrac)+lagvel*ticfrac;
double diffx = flagvel dot (cos(flagangle+90),sin(flagangle+90),0);
double diffy = flagvel dot (0,0,1);
if ( abs(diffx) > 1. )
{
int sgn = (diffx>0)?1:-1;
diffx = abs(diffx)**.5*sgn;
}
if ( abs(diffy) > 1. )
{
int sgn = (diffy>0)?1:-1;
diffy = abs(diffy)**.5*sgn;
}
// don't do inertial sway when in 6dof mode, causes issues
if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',players[consoleplayer]);
if ( !fly6dof.GetBool() || !((waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2)) )
{
cur.x += diffang;
cur.y -= diffpitch;
cur.x += diffx*4.;
cur.y += diffy*4.;
}
return cur*(oldlagready*(1.-ticfrac)+lagready*ticfrac);
}
override void PlayerThink()
{
oldangle = angle;
oldpitch = pitch;
Super.PlayerThink();
oldlagangle = lagangle;
oldlagpitch = lagpitch;
lagangle = lagangle*.8+angle*.2;
lagpitch = lagpitch*.8+pitch*.2;
if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) )
{
// faster falloff
player.jumptics -= 5;
if ( player.onground && (player.jumptics < -18) )
player.jumptics = 0;
}
// quick grenade
if ( (player.playerstate != PST_DEAD) && (player.cmd.buttons&BT_USER4) )
SWWMGesture.SetGesture(self,-1);
}
override void Tick()
{
Super.Tick();
if ( !player ) return;
if ( !selflight )
{
selflight = Spawn("DemolitionistSelfLight",pos);
selflight.target = self;
selflight.Tick();
}
if ( player.mo != self ) return; // no voodoo dolls past this point
// overheal fading
if ( (health <= 200) || (health > oldhealth) )
{
healtimer = 0;
healcooldown = 80;
}
else if ( health > 200 )
{
if ( healcooldown > 0 ) healcooldown--;
else
{
if ( health > 500 )
{
if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%5) )
A_SetHealth(health-1);
}
else if ( health > 200 )
{
if ( !FindInventory("RefresherRegen") && !(healtimer%15) )
A_SetHealth(health-1);
}
healtimer++;
}
}
oldhealth = health;
oldlagvel = lagvel;
oldlagready = lagready;
if ( player.weaponstate&WF_WEAPONBOBBING ) lagready = lagready*.9+.1;
else lagready = lagready*.4;
lagvel = lagvel*.8+vel*.2;
double traveldist = level.Vec3Diff(prev,pos).length();
if ( waterlevel < 2 )
{
if ( !player.onground || bNoGravity )
{
cairtime++;
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
mystats.airdist += traveldist;
}
else
{
cairtime = 0;
mystats.grounddist += traveldist;
}
}
else mystats.swimdist += traveldist;
CheckUnderwaterAmb();
if ( player.cmd.buttons&BT_USER3 ) SenseItems();
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) && (health > 0) )
{
// bump down weapon
bumpdown = true;
bumpvelz = -lastvelz;
if ( lastvelz < -25 )
{
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = int(-lastvelz);
ReactionTime = 17;
A_Stop();
A_AlertMonsters(2500);
if ( player == players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
}
mystats.stompcount++;
}
if ( lastvelz < -10 ) A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( (player == players[consoleplayer]) && (lastvelz < -gruntspeed) && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( lastvelz < -1 )
A_Footstep(0,true,clamp(-lastvelz*0.05,0.0,1.0));
}
// crush anything we're standing on
bool dummy;
Actor encroached;
[dummy, encroached] = TestMobjZ();
// add special check so corpses don't get stuck on top of monsters and players
if ( encroached && encroached.bSOLID && (bSOLID || encroached.bACTLIKEBRIDGE) )
{
// try to follow movement (this method is awkward but works with monsters)
if ( encroached == oldencroached )
{
Vector3 oldp = pos;
Vector3 newp = level.Vec3Offset(pos,level.Vec3Diff(oldencroachedpos,encroached.pos));
if ( level.IsPointInLevel(newp) )
{
SetOrigin(newp,true);
if ( !TestMobjLocation() ) SetOrigin(oldp,true);
}
}
oldencroached = encroached;
oldencroachedpos = encroached.pos;
}
else
{
// launch in movement direction (useful for moving platforms)
// make sure we're not getting launched because an enemy just died under our feet, because that can cause some issues
if ( oldencroached && (dashboost <= 0.) && (lastvelz >= -25) && (!oldencroached.bISMONSTER || (oldencroached.Health > 0)) ) vel += oldencroached.vel+level.Vec3Diff(oldencroachedpos,oldencroached.pos);
oldencroached = null;
}
if ( encroached && encroached.bSHOOTABLE && !encroached.bNODAMAGE && (lastvelz <= 0) && !(encroached is 'Demolitionist') )
{
if ( (lastvelz < -5) || !(level.maptime%5) )
{
int realdmg = encroached.DamageMobj(self,self,int((2+max(0,-lastvelz*3))*max(1.,mass/encroached.mass)),'Jump',DMG_THRUSTLESS);
if ( FindInventory("RagekitPower") )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = realdmg;
}
if ( !encroached.bNOBLOOD && !encroached.bINVULNERABLE )
{
encroached.TraceBleed(realdmg,self);
encroached.SpawnBlood(pos,angle,realdmg);
}
}
}
if ( bumpdown )
{
lagvel.z += bumpvelz*.2;
bumpvelz *= .8;
if ( bumpvelz < double.epsilon ) bumpdown = false;
}
lastground = player.onground;
lastvelz = prevvelz;
prevvelz = vel.z;
bool isdashing = InStateSequence(CurState,FindState("Dash"));
bool isboosting = InStateSequence(CurState,FindState("Jump"));
bNOFRICTION = (((waterlevel<2)&&(bFly||bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||isdashing);
fuelcooldown = max(0,fuelcooldown-1);
dashcooldown = max(0,dashcooldown-1);
boostcooldown = max(0,boostcooldown-1);
if ( fuelcooldown <= 0 )
dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.));
if ( (dashboost > 0.) && (isdashing || (isboosting && player.cmd.buttons&BT_JUMP)) )
dashsnd = true;
else
{
if ( dashsnd )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
dashsnd = false;
}
PainChance = isdashing?0:255;
if ( isdashing || (vel.length() > 50) )
{
Actor a;
if ( isdashing && (dashboost > 0.) )
{
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
{
a = Spawn("DashTrail",Vec3Angle(15,angle+i*140,35));
a.target = self;
a.vel = (RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
Vector3 dir = vel;
double spd = dir.length();
dir = dir/spd;
Vector3 viewdir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
// look for things we could potentially bump into
bool bumped = false;
let bi = BlockThingsIterator.Create(self,500);
let raging = RagekitPower(FindInventory("RagekitPower"));
double maxmass = max(mass*spd/40.,200);
if ( raging ) maxmass *= 2;
while ( (spd > 0) && bi.Next() )
{
a = bi.Thing;
if ( !a || (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS ) continue;
if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*spd,8,16) ) continue;
if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
Vector3 diff = level.Vec3Diff(pos,a.pos);
Vector3 dirto = diff.unit();
if ( dir dot dirto < .1 ) continue;
if ( a.bACTLIKEBRIDGE && (diff.z <= -a.Height) ) continue; // don't bump bridges if hit by above
// large monsters will stop the player (unless hit from above if we're going at ground pound speed)
A_QuakeEx(4,4,4,10,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
A_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 )
{
// see if we can bust it
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = int(128+spd*20);
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
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(15+spd*2.5);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
busted = true;
raging.DoHitFX();
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 )
{
// 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);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel.z += 5+(spd*(10/mass));
vel += ceilnorm*(15+(spd*40/mass));
dashboost *= 0.;
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(15+spd*2.5);
raging.DoHitFX();
}
}
}
}
// check for wall collision
FCheckPosition tm;
Vector2 steppy = dir.xy*spd/8.;
Vector3 oldpos = pos;
bool oldthru = bTHRUACTORS;
bTHRUACTORS = true;
for ( int i=1; i<=8; i++ )
{
if ( (spd > 0) && !bumped && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) && BlockingLine )
{
Vector3 wallnorm = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
int lside = 1;
if ( !BlockingLine.sidedef[1] || !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
{
lside = 0;
wallnorm *= -1;
}
// don't bump if we're only grazing it
if ( dir dot wallnorm > -.6 )
continue;
if ( raging )
{
// see if we can bust it
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = int(128+spd*20);
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
dmg *= 3;
buttslam = true;
}
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
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);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
raging.DoHitFX();
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++;
}
continue;
}
}
// 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);
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 ( 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()
{
bool isdashing = InStateSequence(CurState,FindState("Dash"));
if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0;
if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) )
{
player.onground = false;
if ( player.turnticks )
{
player.turnticks--;
guideangle = (180./TURN180_TICKS);
}
else guideangle += .2*player.cmd.yaw*(360./65536.);
guidepitch -= .2*player.cmd.pitch*(360./65536.);
if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',player);
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
if ( player.centering || !fly6dof.GetBool() ) guideroll = clamp(deltaangle(roll,0),-3.,3.);
if ( fly6dof.GetBool() )
{
swwm_Quat orient = swwm_Quat.create_euler(pitch,angle,roll);
swwm_Quat angles = swwm_Quat.create_euler(guidepitch,guideangle,guideroll);
orient = orient.qmul(angles);
double npitch, nangle, nroll;
[npitch, nangle, nroll] = orient.to_euler();
A_SetAngle(nangle,SPF_INTERPOLATE);
A_SetPitch(npitch,SPF_INTERPOLATE);
A_SetRoll(nroll,SPF_INTERPOLATE);
}
else
{
A_SetAngle(angle+guideangle,SPF_INTERPOLATE);
A_SetPitch(clamp(pitch+guidepitch,player.MinPitch,player.MaxPitch),SPF_INTERPOLATE);
A_SetRoll(roll+guideroll,SPF_INTERPOLATE);
}
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
{
guideroll = guidepitch = roll = pitch = 0.;
player.centering = false;
}
double fs = TweakSpeeds(1.,0.);
double jcmove = 0.;
if ( player.cmd.buttons&BT_JUMP ) jcmove += 4096.;
if ( player.cmd.buttons&BT_CROUCH ) jcmove -= 4096.;
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 accel = x*player.cmd.forwardmove+y*player.cmd.sidemove+z*jcmove;
accel *= fs/320.;
double spd = vel.length();
if ( spd > 40. ) vel = (vel+accel/TICRATE).unit()*spd;
else vel = vel+accel/TICRATE;
vel *= .97;
player.vel = (1,1)*vel.length();
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) && (player.cmd.forwardmove|player.cmd.sidemove) )
PlayRunning();
if ( player.cheats&CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
else
{
Super.MovePlayer();
// override air control because we REALLY need the extra mobility
if ( !player.onground && !bNOGRAVITY && (waterlevel < 2) )
{
double fs = TweakSpeeds(1.,0.);
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 accel = (RotateVector((player.cmd.forwardmove,-player.cmd.sidemove),angle),0);
accel *= fs/512.;
double spd = vel.length();
if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd;
else vel = vel+accel/TICRATE;
}
else if ( player.onground && !bNOGRAVITY && (waterlevel < 2) )
{
// quickly decelerate if we're not holding movement keys
if ( !(player.cmd.forwardmove|player.cmd.sidemove) ) vel *= .97;
}
if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE);
}
guideangle *= .9;
guidepitch *= .9;
guideroll *= .9;
// anchor to ground when going down steps
if ( lastground && !player.onground && !bFly && !bFlyCheat && (waterlevel < 2) && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z < 0) && !isdashing )
{
// test for gap crossing (i.e: climbing up platforms with holes between them)
Vector3 storepos = pos;
double storefloorz = floorz;
bool crossgap = false;
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
{
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),false);
if ( floorz < storepos.z ) continue;
crossgap = true;
break;
}
SetOrigin(storepos,false);
floorz = storefloorz;
if ( !crossgap )
{
ssup = max(0,(pos.z-floorz));
SetOrigin(Vec2OffsetZ(0,0,floorz),true);
lastground = player.onground = true;
}
}
if ( player.onground ) lastgroundtic = level.maptime;
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
PlayIdle();
Vector3 dodge = (0,0,0), x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
int fm = player.cmd.forwardmove;
int sm = player.cmd.sidemove;
if ( !(fm|sm) ) fm = 1;
if ( fm ) dodge += (fm>0)?X:-X;
if ( sm ) dodge += (sm>0)?Y:-Y;
if ( player.cmd.buttons&BT_CROUCH ) dodge = (0,0,-1); // death from above
if ( dodge == (0,0,0) ) dodge.xy = RotateVector((1,0),angle);
if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z);
if ( player.onground ) dodge.z = max(0,dodge.z);
if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && (player.onground || level.IsJumpingAllowed() || (player.cmd.buttons&BT_CROUCH)) && (gamestate == GS_LEVEL) )
{
dashdir = dodge.unit();
dashcooldown = 10;
dashboost = 20.;
bOnMobj = false;
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
vel.z += clamp(-vel.z*.4,0.,30.);
player.jumptics = -1;
SetStateLabel("Dash");
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
mystats.dashcount++;
}
}
override void CheckJump()
{
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
Vector2 walldir = (cos(angle),sin(angle));
bool walljump = false, wallclimb = false;
FLineTraceData d;
Actor jumpactor = null;
for ( int i=-4; i<int(height*.8); i++ )
{
if ( !wallclimb )
{
for ( int k=0; k<30; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = angle+k*j;
wallclimb = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( wallclimb )
{
jumpactor = d.HitActor;
walldir = (walldir*.7+(cos(ang),sin(ang))*.3);
break;
}
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
if ( !walljump )
{
for ( int k=0; k<60; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = (angle-180)+k*j;
walljump = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( walljump )
{
jumpactor = d.HitActor;
walldir = (walldir*.7+(cos(ang+180),sin(ang+180))*.3);
break;
}
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( (player.cmd.buttons&BT_JUMP) && (gamestate == GS_LEVEL) )
{
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
if ( level.maptime < (lastgroundtic+4) )
{
walljump = false;
wallclimb = false;
jumpactor = null;
}
if ( player.crouchoffset ) player.crouching = 1;
else if ( waterlevel >= 2 ) vel.z = 4*Speed;
else if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) ) return;
else if ( bNoGravity ) vel.z = 3;
else if ( level.IsJumpingAllowed()
&& ((player.onground && (player.jumptics == 0))
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) )
{
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
return;
double jumpvelz = JumpZ*35./TICRATE;
double jumpfac = 0;
for ( let p=Inv; p; p=p.Inv )
{
let pp = PowerHighJump(p);
if ( pp )
{
double f = pp.Strength;
if ( f > jumpfac ) jumpfac = f;
}
}
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
if ( FindInventory("RagekitPower") ) jumpvelz *= 2.;
double pvelz = vel.z;
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
{
// check for wall stuff
if ( walljump )
{
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.6,0.,30.);
vel.xy += walldir*20*Speed;
}
else if ( wallclimb )
{
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.8,0.,30.);
vel.xy -= walldir*5*Speed;
}
if ( jumpactor && jumpactor.bSHOOTABLE )
{
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
if ( FindInventory("RagekitPower") )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = dmg;
}
}
if ( walljump || wallclimb )
{
A_StartSound("demolitionist/kick",CHAN_FOOTSTEP,CHANF_OVERLAP);
last_kick = level.maptime+1;
}
}
bOnMobj = false;
player.jumpTics = -1;
if ( !(player.cheats&CF_PREDICTING) )
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
{
dashboost = 3.;
boostcooldown = 20;
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
mystats.boostcount++;
last_boost = level.maptime+1;
}
else
{
dashboost = 0.;
if ( vel.z < 10. )
vel.z += jumpvelz*1.25;
}
SetStateLabel("Jump");
}
last_jump_held = level.maptime+1;
}
}
override void DeathThink()
{
player.Uncrouch();
TickPSprites();
player.onground = (pos.Z<=floorz);
// ded (demo-chan falls faster tho)
player.deltaviewheight = 0;
if ( player.viewheight > 6 ) player.viewheight -= 3;
if ( player.viewheight < 6 ) player.viewheight = 6;
// center pitch
double dpitch = clamp(deltaangle(pitch,0),-6,6);
if ( abs(dpitch) < 3. ) pitch = 0.;
else A_SetPitch(pitch+dpitch,SPF_INTERPOLATE);
// add roll
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
if ( abs(droll) < 2. ) roll = 50.;
else A_SetRoll(roll+droll,SPF_INTERPOLATE);
player.mo.CalcHeight();
if ( player.damagecount ) player.damagecount--;
if ( player.poisoncount ) player.poisoncount--;
if ( player.viewheight <= 6 )
{
deadtimer++;
if ( (deadtimer == 60) && (player == players[consoleplayer]) )
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI);
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (((player.cmd.buttons&BT_USE) && !player.Bot) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn && (Level.maptime >= player.respawn_time))) && !sv_norespawn )
{
player.cls = null;
player.playerstate = PST_REBORN;
if ( special1 > 2 ) special1 = 0;
}
}
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) )
{
// reload save
player.cls = null;
player.playerstate = PST_ENTER;
if ( special1 > 2 ) special1 = 0;
}
else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 120) && swwm_revive )
{
// reboot (if possible)
if ( !FindInventory("ReviveCooldown") )
{
player.Resurrect();
SetState(FindState("Spawn")+1); // skip tweening
roll = 0;
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = 30;
ReactionTime = 17;
A_Stop();
A_AlertMonsters(2500);
if ( player == players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
}
SWWMHandler.DoFlash(self,Color(255,255,255,255),10);
SWWMHandler.DoFlash(self,Color(255,128,192,255),30);
SWWMScoreObj.Spawn(default.health,Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Height/2),Font.CR_BLUE);
if ( special1 > 2 ) special1 = 0;
if ( swwm_revivecooldown > 0 )
GiveInventory("ReviveCooldown",1);
}
else if ( level.maptime > revivefail )
{
if ( player == players[consoleplayer] ) A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI);
revivefail = level.maptime+120;
}
}
}
else deadtimer = 0;
}
override void PlayIdle()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("Spawn")) )
SetStateLabel("Spawn");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("Crouch");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("Float")) )
{
SetStateLabel("Spawn");
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
}
else if ( InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop")) )
SetStateLabel("SeeFastEnd");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("SwimEnd");
}
}
else if ( !bNoGravity && (waterlevel < 1) )
{
// Falling
if ( player.crouchdir == -1 )
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("StartCrouch");
}
else
{
if ( (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float")))
&& (abs(pos.z-floorz) > maxstepheight) )
SetStateLabel("Fall");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("SwimEnd");
}
}
else
{
// Swimming
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop")) )
SetStateLabel("Float");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("SwimEnd");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
}
}
}
override void PlayRunning()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("See")) )
SetStateLabel("See");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
SetStateLabel("CrouchMove");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop")) )
SetStateLabel("SwimEnd");
else if ( FastCheck()
&& (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))) )
SetStateLabel("SeeFast");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("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 ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float")) )
SetStateLabel("Swim");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
}
}
}
override void PlayAttacking()
{
// Do nothing if it's a SWWM weapon, since those do things themselves
if ( player && (player.ReadyWeapon is 'SWWMWeapon') )
return;
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
override void PlayAttacking2()
{
PlayAttacking();
}
void PlayFire()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
void PlayMelee()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMelee");
else SetStateLabel("Melee");
}
void PlayFastMelee()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastMelee");
else SetStateLabel("FastMelee");
}
void PlayReload()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchReload");
else SetStateLabel("Reload");
}
void PlayCheckGun()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Jump")) )
return; // don't cancel dash/jump
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchCheckGun");
else SetStateLabel("CheckGun");
}
void A_DMFade()
{
if ( player ) return;
Spawn("TeleportFog",pos,ALLOW_REPLACE);
Destroy();
}
void A_DemoPain()
{
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
if ( lastdamage > 90 )
{
A_StartSound("demolitionist/hipain",CHAN_VOICE);
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
else if ( lastdamage > 30 )
{
A_StartSound("demolitionist/pain",CHAN_VOICE);
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
else if ( lastdamage > 0 )
{
A_StartSound("demolitionist/lopain",CHAN_VOICE);
if ( !player || (player.mo != self) || (lastdamage < 5) ) return; // voodoo dolls have no voice
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
}
void A_DemoScream()
{
A_StopSound(CHAN_DEMOVOICE);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
Sound snd = "demolitionist/death";
if ( special1 < 10 )
snd = "demolitionist/wdeath";
if ( health < 50 )
snd = "demolitionist/xdeath";
A_StartSound(snd,CHAN_VOICE);
if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice
if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) )
A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
}
override bool OnGiveSecret( bool printmsg, bool playsound )
{
if ( !player ) return false;
int score = 100;
// last secret (this is called before counting it up, so have to subtract)
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
{
if ( hnd ) hnd.allsecrets = true;
score = 1000;
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),player.GetUserName(),score);
}
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score);
if ( CheckLocalView() ) SWWMHandler.AddOneliner("findsecret",2,40);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(score,Vec3Offset(0,0,Height/2));
mystats.secrets++;
return true;
}
override void AddInventory( Inventory item )
{
Super.AddInventory(item);
if ( !player ) return;
// add lore if any
SWWMLoreLibrary.Add(player,item.GetClassName());
if ( (item is 'Weapon') && !(item is 'SWWMGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() )
SWWMHandler.AddOneliner("getweapon",2);
if ( (item is 'Key') && !key_reentrant && !deathmatch )
{
// score
int score = 100;
Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(100,Vec3Offset(0,0,Height/2));
if ( !swwm_sharekeys ) return;
// share all keys in mp
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo || (i == PlayerNumber()) )
continue;
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = true;
players[i].mo.GiveInventory(item.GetClass(),1);
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = false;
}
}
}
override bool UseInventory( Inventory item )
{
let itemtype = item.GetClass();
if ( (player.cheats&CF_TOTALLYFROZEN) || isFrozen() ) return false;
if ( !Actor.UseInventory(item) )
{
if ( player == players[consoleplayer] )
{
if ( !(item is 'Weapon') )
A_StartSound("menu/noinvuse",CHAN_ITEMEXTRA);
if ( item is 'PuzzleItem' )
SWWMHandler.AddOneliner("puzzfail",2,20);
}
return false;
}
// use sounds of big powerups are heard by other players
if ( (player == players[consoleplayer]) || item.bBIGPOWERUP )
A_StartSound(item.UseSound,CHAN_ITEMEXTRA);
if ( (player == players[consoleplayer]) && (item is 'PuzzleItem') )
SWWMHandler.AddOneliner("puzzsucc",2,10);
return true;
}
void A_Footstep( double yofs, bool run = false, double vol = .3 )
{
if ( run )
{
A_StartSound("demolitionist/run",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.A_CheckTerrain();
}
else
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
let b = Spawn("SmolInvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.A_CheckTerrain();
}
}
override bool Used( Actor user )
{
if ( !(user is 'Demolitionist') || !player || (player.mo != self) ) return false;
if ( (user.player == players[consoleplayer]) && (health > 0) )
{
SWWMHandler.AddOneliner("greet",2);
return true;
}
return false;
}
bool FastCheck( bool notfast = false )
{
if ( !player ) return false;
bool rslt = max(abs(player.cmd.forwardmove),abs(player.cmd.sidemove))>6400;
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;
}
/* // requires PR #1169
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"));
}
}
*/
/* // requires PR #1173
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;
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;
XZWE Z 3;
XZWF ABCDEFGHIJKLMN 3;
Goto Crouch+1;
CrouchApprove:
#### # 3;
XZWF OPQRSTUVWXYZ 3;
XZWG ABCDE 3;
Goto Crouch+1;
CrouchVictory:
#### # 3;
XZWG FGHIJKLMNOPQRSTUVWX 3;
Goto Crouch+1;
CrouchBlowKiss:
#### # 3;
XZWG YZ 3;
XZWH ABCDEFGHIJKLMNOPQ 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 WXYZ 3;
XZWE ABCDEFGH 3;
Goto Float+1;
Swim:
#### # 2;
XZWE IJK 2;
Goto SwimLoop;
SwimLoop:
XZWE LMN 2;
XZWE O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZWE OPQRST 2;
XZWE U 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZWE UVW 2;
Loop;
SwimEnd:
#### # 2;
XZWE LXY 2;
Goto Float+1;
VoodooSpawn:
XZWZ A -1;
Loop;
VoodooPain:
XZWZ A 1;
XZWZ B 1 A_DemoPain();
XZWZ CDEF 1;
Goto VoodooSpawn;
VoodooDeath:
XZWZ A 2
{
A_DemoScream();
A_NoBlocking();
}
XZWZ GHIJKLMNO 2;
XZWZ PQR 2;
XZWZ S -1;
Stop;
}
}
Class DashTrail : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
Scale 0.3;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetState(FindState("Spawn")+Random[ExploS](0,7));
let t = Spawn("DashTrail2",level.Vec3Offset(pos,vel*.3));
t.target = target;
t.vel = vel*1.2;
let s = Spawn("SWWMSmoke",level.Vec3Offset(pos,vel*1.6));
s.vel = vel*.8;
s.SetShade(Color(1,1,1)*Random[ExploS](64,128));
s.special1 = Random[ExploS](2,4);
s.scale *= 1.4;
s.alpha *= .3;
}
override void Tick()
{
Super.Tick();
// hack
if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION);
}
States
{
Spawn:
JFLB ABCDEFGH 1 Bright
{
A_FadeOut(.2);
A_SetScale(scale.x*.95);
}
Loop;
}
}
Class DashTrail2 : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
Scale 0.2;
Alpha 0.4;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetState(FindState("Spawn")+Random[ExploS](0,7));
}
override void Tick()
{
Super.Tick();
// hack
if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION);
}
States
{
Spawn:
JFLR ABCDEFGH 1 Bright
{
A_FadeOut(.02);
A_SetScale(scale.x*1.04);
if ( waterlevel > 0 )
{
let b = Spawn("SWWMBubble",pos);
b.vel = vel;
b.scale *= scale.x;
Destroy();
}
}
Loop;
}
}
Class DemolitionistRadiusShockwaveTail : Actor
{
Default
{
RenderStyle "Add";
Radius 16;
Height 8;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
}
States
{
Spawn:
XZW1 A 1
{
pitch = min(85,(pitch+2)*1.05);
A_FadeOut(.02);
A_SetScale(scale.x*1.08,scale.y);
vel *= .98;
}
Wait;
}
}
Class DemolitionistRadiusShockwave : Actor
{
Actor lasthit;
Default
{
RenderStyle "Add";
Speed 15;
DamageFunction int(200*alpha);
DamageType "GroundPound";
Radius 16;
Height 8;
Alpha .4;
XScale .65;
YScale 3.;
PROJECTILE;
+DONTSPLASH;
+STEPMISSILE;
+NOEXPLODEFLOOR;
+FLATSPRITE;
+RIPPER;
+BLOODLESSIMPACT;
-NOGRAVITY;
}
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
{
if ( target == lasthit ) return 0;
lasthit = target;
if ( damage <= 0 ) return damage;
if ( (target.mass < LARGE_MASS) && !target.bDONTTHRUST )
{
target.vel.xy += vel.xy.unit()*(30000./max(50,target.mass))*alpha;
if ( (target.pos.z <= floorz) || !target.TestMobjZ() )
target.vel.z += (4000./max(50,target.mass))*alpha;
}
return damage;
}
States
{
Spawn:
XZW1 A 1
{
SetZ(floorz);
pitch = min(85,(pitch+2)*1.05);
if ( !Random[ExploS](0,3) )
Spawn("InvisibleSplasher",Vec3Offset(0,0,2));
let s = Spawn("DemolitionistRadiusShockwaveTail",pos);
s.vel = vel*.35;
s.scale = scale;
s.alpha = alpha*.4;
s.angle = angle;
s.pitch = pitch;
s.roll = roll;
A_FadeOut(.015);
A_SetScale(scale.x*1.08,scale.y);
vel *= .98;
}
Wait;
Death:
XZW1 A 1
{
SetZ(floorz);
A_FadeOut(.05);
A_SetScale(scale.x*1.1,scale.y*.97);
}
Wait;
}
}
Class DemolitionistShockwave : Actor
{
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(7,7,7,30,0,300+min(special1,50)*4,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:1.5);
if ( target.player != players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.3);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.2,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.1,pitch:.4);
}
SWWMUtility.DoExplosion(self,40+min(special1,120),100000+min(special1*2000,150000),100+min(special1*2,130),80,DE_BLAST|DE_EXTRAZTHRUST,'GroundPound',target);
for ( int i=0; i<360; i+=5 )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
let s = Spawn("SWWMSmoke",Vec3Angle(4,i,8));
s.vel = pvel+(cos(i),sin(i),0)*7.;
s.SetShade(Color(1,1,1)*Random[ExploS](64,224));
s.special1 = Random[ExploS](1,4);
s.scale *= 1.5;
s.alpha *= .4;
}
if ( pos.z > floorz+16 ) return;
for ( int i=0; i<360; i+=5 )
{
let r = Spawn("DemolitionistRadiusShockwave",Vec3Angle(5,i));
r.target = target;
r.angle = i;
r.vel.xy = (cos(i),sin(i))*(r.speed+min(special1*.15,30));
r.alpha *= .1+min(special1*.03,.9);
}
int numpt = Random[ExploS](10,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
let raging = RagekitPower(target.FindInventory("RagekitPower"));
if ( raging )
{
// bust the floor if we can
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = (40+min(special1,120))*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);
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
{
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOINTERACTION;
}
bool activelight()
{
// active all the time except when invisible or in certain
// animation frames
if ( target.bINVISIBLE || (target.alpha <= double.epsilon) ) return false;
if ( target.InStateSequence(target.CurState,target.FindState("Death")) && ((target.frame == 20) || (target.frame == 22) || (target.frame == 23) || (target.frame == 25) || (target.frame == 27) || (target.frame == 28) || (target.frame == 30) || (target.frame == 31) || (target.frame == 32) || (target.frame == 33) || (target.frame == 34)) )
return false;
if ( target.InStateSequence(target.CurState,target.FindState("CrouchDeath")) && ((target.frame == 7) || (target.frame == 10) || (target.frame == 11)) )
return false;
return true;
}
override void Tick()
{
if ( !target || !(target is 'Demolitionist') || (Demolitionist(target).selflight != self) )
{
Destroy();
return;
}
if ( !activelight() )
target.A_RemoveLight('DemoSelfLight');
else if ( activelight() )
target.A_AttachLight('DemoSelfLight',DynamicLight.PointLight,Color(56,72,88),200,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE|DynamicLight.LF_SPOT,(12,0,target.player?(target.player.viewz-target.pos.z):(target.height*.93)),0,30,90,target.pitch);
if ( target.bINVISIBLE || (target.alpha <= double.epsilon) )
target.A_RemoveLight('DemoSelfLight2');
else
target.A_AttachLight('DemoSelfLight2',DynamicLight.PointLight,Color(32,48,24),80,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE,(0,0,target.height/2));
}
}
// 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 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 ( 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
target.bFRIENDLY = true;
if ( deathmatch )
target.SetFriendPlayer(self.target.player);
}
return 0;
}
let bread = target.FindState("Pain");
if ( bread ) target.SetState(bread);
if ( raging )
{
damage *= 8;
raging.DoHitFX();
}
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);
special2 = 25;
}
action void A_HeartBurst()
{
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 HHitList
{
Actor a;
Vector3 dir;
}
// First person gestures
Class SWWMGesture : SWWMWeapon
{
Weapon formerweapon;
int whichgesture;
bool deaded;
int gonect;
// these should prevent autoswitch when out of ammo
override bool ReportHUDAmmo()
{
return false;
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
return false;
}
static void SetGesture( PlayerPawn mo, int which = 0 )
{
if ( (mo.player.ReadyWeapon is 'SWWMGesture') || (mo.player.PendingWeapon is 'SWWMGesture') ) return; // already gesturing
SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture"));
if ( !w )
{
w = SWWMGesture(Spawn("SWWMGesture"));
mo.AddInventory(w);
}
if ( mo.player.PendingWeapon != WP_NOCHANGE ) w.formerweapon = mo.player.PendingWeapon;
else w.formerweapon = mo.player.ReadyWeapon;
w.whichgesture = which;
mo.player.PendingWeapon = w;
}
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()
{
player.PendingWeapon = invoker.formerweapon;
player.SetPSprite(PSP_WEAPON,ResolveState("Deselect"));
}
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.WIMPY_WEAPON;
+SWWMWEAPON.HIDEINMENU;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNCLEARABLE;
Weapon.SelectionOrder int.max;
}
States
{
Select:
TNT1 A 1
{
A_FullRaise();
if ( Health <= 0 ) A_FinishGesture();
}
Goto Ready;
Ready:
TNT1 A 1
{
switch ( invoker.whichgesture )
{
case -1:
return ResolveState("QuickGrenade");
case 1:
return ResolveState("Approve");
case 2:
return ResolveState("Victory");
case 3:
return ResolveState("BlowKiss");
}
return ResolveState("Wave");
}
Wait;
Fire:
TNT1 A 1;
Goto Ready;
QuickGrenade:
TNT1 A 3;
XZW3 S 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 TU 3;
XZW3 V 3 A_PlayerReload();
XZW3 W 2 A_StartSound("explodium/magpin",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 XY 2;
XZW3 Z 3;
XZW4 A 2
{
A_StartSound("explodium/throwmag",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW4 BC 2;
XZW4 DEFGH 2;
XZW4 I 2 A_ThrowMag();
XZW4 JKLM 3;
TNT1 A 4;
TNT1 A 0 A_JumpIf(player.cmd.buttons&BT_USER4,"QuickGrenade");
TNT1 A -1 A_FinishGesture();
Stop;
Wave:
TNT1 A 3 A_CallPlayerGesture("Wave","CrouchWave");
XZW1 AB 3;
XZW1 C 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW1 DEFGHIJ 3;
XZW1 K 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW1 LMNO 3;
TNT1 A -1 A_FinishGesture();
Stop;
Approve:
TNT1 A 3 A_CallPlayerGesture("Approve","CrouchApprove");
XZW1 PQ 3;
XZW1 R 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW1 STUVWX 3;
XZW1 Y 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW1 Z 3;
XZW2 ABCDEF 3;
TNT1 A -1 A_FinishGesture();
Stop;
Victory:
TNT1 A 3 A_CallPlayerGesture("Victory","CrouchVictory");
XZW2 GH 3;
XZW2 I 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 JKLMNOPQ 3;
XZW2 R 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 STUVWXY 3;
TNT1 A -1 A_FinishGesture();
Stop;
BlowKiss:
TNT1 A 3 A_CallPlayerGesture("BlowKiss","CrouchBlowKiss");
XZW2 Z 3;
XZW3 A 3;
XZW3 B 3 A_StartSound("demolitionist/handsup",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 CD 3;
XZW3 E 3 A_Smooch();
XZW3 FGHI 3;
XZW3 J 3 A_StartSound("demolitionist/handsdown",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 K 3 A_BlowKiss();
XZW3 LMNOPQR 3;
TNT1 A -1 A_FinishGesture();
Stop;
Deselect:
TNT1 A -1 A_FullLower();
Stop;
}
}