swwmgz_m/zscript/swwm_player.zsc
Marisa Kirisame e5883dac2f Fix up how inventory use sounds are played, so they don't get loudened anymore.
Fix up how auto-healing works, so now if you have 5 refreshers you actually can survive 2500 damage, as one would expect.
2020-03-08 12:23:27 +01:00

1611 lines
47 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;
SWWMStats mystats;
int cairtime;
int lastmpain;
double guideangle, guidepitch, guideroll;
enum EUnderType
{
UNDER_NONE,
UNDER_WATER,
UNDER_SLIME,
UNDER_LAVA
};
int lastunder;
int deadtimer;
transient int revivefail;
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;
+NOBLOOD;
+DONTGIB;
+NOICEDEATH;
}
// 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 = 1000;
}
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;
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 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;
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 == 'GroundPound' ) return StringTable.Localize("$O_POUND");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override void GiveDefaultInventory()
{
Super.GiveDefaultInventory();
// preloaded gun
let dp = DeepImpact(FindInventory("DeepImpact"));
if ( dp ) SWWMLoreLibrary.Add(player,"DeepImpact");
let eg = ExplodiumGun(FindInventory("ExplodiumGun"));
if ( !eg ) return;
SWWMLoreLibrary.Add(player,"ExplodiumGun");
eg.clipcount = 7;
eg.chambered = true;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
mystats = SWWMStats.Find(player);
lastground = true;
}
void A_Dash()
{
vel += dashdir*dashboost;
player.vel = vel.xy;
if ( dashboost < 0.1 ) dashboost = 0.;
else
{
A_AlertMonsters(800);
dashboost *= .5;
}
mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost);
dashfuel = max(0.,dashfuel-dashboost);
dashcooldown = 40;
fuelcooldown = max(60,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 < 0.1 ) dashboost = 0.;
else
{
A_AlertMonsters(800);
dashboost *= (player.cmd.buttons&BT_JUMP)?.9:.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 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 void Tick()
{
Super.Tick();
if ( !player ) return;
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;
}
}
CheckUnderwaterAmb();
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) )
{
if ( lastvelz < -30 )
{
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = int(-lastvelz);
ReactionTime = 17;
A_Stop();
A_AlertMonsters();
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));
}
lastground = player.onground;
lastvelz = prevvelz;
prevvelz = vel.z;
bNOFRICTION = (((waterlevel<2)&&(bFly||bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||InStateSequence(CurState,FindState("Dash")));
fuelcooldown = max(0,fuelcooldown-1);
dashcooldown = max(0,dashcooldown-1);
boostcooldown = max(0,boostcooldown-1);
if ( fuelcooldown <= 0 )
dashfuel = min(120.,dashfuel+max(dashfuel*.01,.05));
if ( (dashboost > 0.) && (InStateSequence(CurState,FindState("Dash")) || (InStateSequence(CurState,FindState("Jump")) && player.cmd.buttons&BT_JUMP)) )
dashsnd = true;
else
{
if ( dashsnd )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
dashsnd = false;
}
PainChance = InStateSequence(CurState,FindState("Dash"))?0:255;
if ( dashboost <= 0. ) return;
if ( InStateSequence(CurState,FindState("Dash")) )
{
Actor a;
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+dashdir*dashboost;
double spd = dir.length();
dir = dir.unit();
// look for things we could potentially bump into
let bi = BlockThingsIterator.Create(self,200);
bool bumped = false;
while ( (spd > 0) && bi.Next() )
{
a = bi.Thing;
if ( !a || (a == self) || !a.bSHOOTABLE || a.bTHRUACTORS ) continue;
Vector3 diff = level.Vec3Diff(level.Vec3Offset(pos,vel),level.Vec3Offset(a.pos,a.vel));
if ( (abs(diff.x) > (radius+a.radius+8)) || (abs(diff.y) > (radius+a.radius+8)) ) continue;
if ( (diff.z > (height+8)) || (diff.z < (8-a.height)) ) continue;
double moveang = atan2(dir.y,dir.x);
Vector3 sc = level.SphericalCoords(pos,a.pos,(moveang,0));
if ( abs(sc.x) > 60 ) continue;
// large monsters will stop the player
A_QuakeEx(1,1,1,3,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
a.A_StartSound("demolitionist/bump",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( a.bDONTTHRUST || (a.Mass >= Mass*5) )
{
if ( bumped ) continue;
bumped = true;
A_QuakeEx(3,3,3,8,0,128,"",QF_RELATIVE|QF_SCALEDOWN);
A_StartSound("demolitionist/bump",CHAN_FOOTSTEP,CHANF_OVERLAP);
vel -= dir*(9+(spd*30/mass));
vel.z += 3+(spd*(20/mass));
}
Vector3 pushdir = Vec3To(a).unit()*.1+dir*.9;
if ( !a.bDONTTHRUST && (a.Mass < Mass*5) )
{
a.vel += pushdir*(15+(spd*150/max(50,a.mass)));
a.vel.z += 5+(spd*(50/max(50,a.mass)));
}
a.DamageMobj(self,self,int(15+spd*2.5),'Dash',DMG_THRUSTLESS);
}
}
else if ( InStateSequence(CurState,FindState("Jump")) )
{
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 )
{
// lucky collar
if ( Health < 25 ) damage /= 4;
if ( source == self ) damage /= 2;
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();
}
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 CheckCrouch( bool totallyfrozen )
{
// crouch to swim/float down
if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) )
{
double mult = (player.cmd.buttons&BT_SPEED)?2.:1.;
if ( waterlevel >= 2 ) vel.z = -4*Speed*mult;
else if ( bNOGRAVITY ) vel.z = -3*mult;
}
Super.CheckCrouch(totallyfrozen);
}
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 ( InStateSequence(CurState,FindState("Dash")) )
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',players[consoleplayer]);
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();
angle = nangle;
pitch = npitch;
roll = nroll;
}
else
{
angle += guideangle;
pitch += guidepitch;
pitch = clamp(pitch,player.MinPitch,player.MaxPitch);
roll += guideroll;
}
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 ( player.cmd.buttons&BT_SPEED ) jcmove *= 2.;
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/960.;
double spd = vel.length();
if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd;
else vel = vel+accel/TICRATE;
}
if ( abs(roll) > 0. ) roll += clamp(deltaangle(roll,0),-3.,3.);
}
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) )
{
ssup = max(0,(pos.z-floorz));
SetOrigin(Vec2OffsetZ(0,0,floorz),true);
lastground = player.onground = true;
}
if ( player.onground ) lastgroundtic = gametic;
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.onground ) dodge.z = max(0,dodge.z);
if ( dodge == (0,0,0) ) dodge.xy = RotateVector((1,0),angle);
if ( (waterlevel < 2) && !(bFly || bFlyCheat) )
dodge.z += .15;
if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && level.IsJumpingAllowed() )
{
dashdir = dodge.unit();
dashcooldown = 40;
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 )
{
double mult = (player.cmd.buttons&BT_SPEED)?2.:1.;
if ( player.crouchoffset ) player.crouching = 1;
else if ( waterlevel >= 2 ) vel.z = 4*Speed*mult;
else if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) ) return;
else if ( bNoGravity ) vel.z = 3*mult;
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;
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 )
{
SWWMHandler.DoKnockback(jumpactor,(-walldir,0),12000);
jumpactor.DamageMobj(self,self,10,'Jump');
}
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 pitch += dpitch;
// add roll
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
if ( abs(droll) < 2. ) roll = 50.;
else roll += droll;
player.mo.CalcHeight();
if ( player.damagecount ) player.damagecount--;
if ( player.poisoncount ) player.poisoncount--;
if ( player.viewheight <= 6 )
{
deadtimer++;
if ( deadtimer == 60 )
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI|CHANF_LOCAL);
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot) )
{
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();
roll = 0;
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = 30;
ReactionTime = 17;
A_Stop();
A_AlertMonsters();
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);
if ( special1 > 2 ) special1 = 0;
if ( swwm_revivecooldown > 0 )
GiveInventory("ReviveCooldown",1);
}
else if ( level.maptime > revivefail )
{
A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI|CHANF_LOCAL);
revivefail = level.maptime+70;
}
}
}
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("FloatLoop")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("FloatLoop")) )
{
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 ( !bNoGravity && (waterlevel < 1) )
{
// Falling
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("FloatLoop")))
&& (abs(pos.z-floorz) > maxstepheight) )
SetStateLabel("Fall");
}
else
{
// Floating
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("FloatLoop")) )
SetStateLabel("Fall");
}
}
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")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
SetStateLabel("CrouchMove");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove")) )
SetStateLabel("EndCrouch");
else if ( player.cmd.buttons&BT_SPEED
&& (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
{
// Floating
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");
}
}
override void PlayAttacking()
{
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 A_DMFade()
{
if ( !deathmatch || player ) return;
A_FadeOut(0.03);
}
void A_DemoPain()
{
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player);
if ( lastdamage > 90 )
{
A_StartSound("*pain25",CHAN_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("*pain50",CHAN_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("*pain100",CHAN_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);
A_PlayerScream();
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 = 500;
// last secret (this is called before counting it up, so have to subtract)
if ( level.found_secrets == level.total_secrets-1 )
{
score = 5000;
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);
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') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() )
SWWMHandler.AddOneliner("getweapon",2);
if ( (item is 'Key') && !key_reentrant )
{
// score
int score = 250;
Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score);
SWWMCredits.Give(player,score);
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') || !user.player ) return false;
if ( (user.player == players[consoleplayer]) && (health > 0) )
{
SWWMHandler.AddOneliner("greet",2);
return true;
}
return false;
}
States
{
Spawn:
// normal idle
#### # 2;
XZW1 A 1 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(player&&(player.cmd.buttons&BT_SPEED),"SeeFast");
XZW1 E 0 A_Footstep(1);
XZW1 EFGHIJKL 2 A_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"SeeFast");
XZW1 M 0 A_Footstep(-1);
XZW1 MNOPA 2 A_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"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(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd");
XZW2 A 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd");
XZW2 B 0 A_Footstep(-1,true);
XZW2 BCDEFG 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd");
XZW1 T 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"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 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 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 350;
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;
Float:
// start
#### # 2;
XZW4 O 3;
Goto FloatLoop;
FloatLoop:
XZW4 PQRS 6;
Loop;
FloatEnd:
#### # 2;
XZW4 TUVWX 3;
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;
Taunt:
#### # 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;
Missile:
Melee:
// attacking
XZW1 A 2;
XZW6 XYZ 2;
XZW7 ABC 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;
CrouchMissile:
XZW7 M 2;
XZW7 WXYZ 2;
XZW8 AB 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 350;
XZW8 L 1 A_DMFade();
Wait;
EndCrouch:
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
XZW8 MNOPQRS 2;
Goto Spawn+1;
}
}
Class DashTrail : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
Scale 0.3;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
}
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;
}
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 "Translucent";
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+WALLSPRITE;
+NOTELEPORT;
}
States
{
Spawn:
SDST A 1
{
A_FadeOut(0.08);
A_SetScale(scale.x,scale.y*0.98);
}
Wait;
}
}
Class DemolitionistRadiusShockwave : Actor
{
Default
{
RenderStyle "Translucent";
Speed 10;
DamageFunction int(20*alpha);
DamageType "GroundPound";
Radius 16;
Height 8;
Alpha 0.8;
Scale 1.2;
+NOBLOCKMAP;
+DONTSPLASH;
+MISSILE;
+STEPMISSILE;
+NOEXPLODEFLOOR;
+WALLSPRITE;
+NOTELEPORT;
+RIPPER;
}
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
{
if ( damage <= 0 ) return damage;
if ( (target.mass < LARGE_MASS) && !target.bDONTTHRUST )
{
target.vel.xy += vel.xy.unit()*(5000./max(50,target.mass))*alpha;
target.vel.z += (800./max(50,target.mass))*alpha;
}
return damage;
}
States
{
Spawn:
SDST A 1
{
SetZ(floorz);
Spawn("InvisibleSplasher",Vec3Offset(0,0,2));
let s = Spawn("DemolitionistRadiusShockwaveTail",pos);
s.vel = vel*.8;
s.scale = scale;
s.alpha = alpha;
s.angle = angle;
A_FadeOut(.04);
A_SetScale(scale.x*1.03,scale.y*0.99);
}
Wait;
Death:
SDST A 1
{
SetZ(floorz);
A_FadeOut(.1);
A_SetScale(scale.x*1.06,scale.y*0.98);
}
Wait;
}
}
Class DemolitionistShockwave : Actor
{
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+NOTELEPORT;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
}
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);
}
target.A_Blast(BF_AFFECTBOSSES,200+min(special1*3,100),70+min(special1*2,130),10+min(special1/10,20),"SWWMNothing","");
A_Explode(10+min(special1,90),70+min(special1*2,130),XF_EXPLICITDAMAGETYPE,damagetype:'GroundPound');
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;
r.alpha *= .1+min(special1*.03,.9);
}
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;
}
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;
}
}
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
}
}