Rebalanced Candygun. Added vanilla boss healthbars + vanilla boss boosting (both optional). Adjustments so the E1M8 exit and similar work fine with this mod. Various language changes (mostly vanilla monster tags). Doubled damage of Pusher altfire, so it matches the projectile's. Adjusted minimum fall speed for stomping. Other small fixed and adjustments.
1780 lines
52 KiB
Text
1780 lines
52 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;
|
|
|
|
// 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;
|
|
|
|
Actor selflight;
|
|
|
|
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;
|
|
// 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 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 ( type is 'Chancebox' ) continue; // do not give these until they're fully implemented
|
|
if ( !(gameinfo.gametype&GAME_HEXEN) && (type is 'AmmoFabricator') ) continue; // no fabricators outside of hexen
|
|
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 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;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !player ) return;
|
|
if ( !selflight )
|
|
{
|
|
selflight = Spawn("DemolitionistSelfLight",pos);
|
|
selflight.target = self;
|
|
selflight.Tick();
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
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) )
|
|
{
|
|
// 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();
|
|
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));
|
|
}
|
|
if ( bumpdown )
|
|
{
|
|
lagvel.z += bumpvelz*.2;
|
|
bumpvelz *= .8;
|
|
if ( bumpvelz < double.epsilon ) bumpdown = false;
|
|
}
|
|
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)));
|
|
}
|
|
int flg = DMG_THRUSTLESS;
|
|
if ( FindInventory("RagekitPower") ) flg |= DMG_FOILINVUL;
|
|
a.DamageMobj(self,self,int(15+spd*2.5),'Dash',flg);
|
|
}
|
|
}
|
|
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;
|
|
if ( !inflictor && !source && (CurSector.flags&Sector.SECF_ENDLEVEL) )
|
|
{
|
|
// end level hax
|
|
damage = max(50,health-100);
|
|
flags |= DMG_FORCED|DMG_NO_ARMOR;
|
|
mod = 'EndLevel';
|
|
}
|
|
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+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("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)
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
|
|
{
|
|
if ( hnd ) hnd.allsecrets = true;
|
|
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
|
|
}
|
|
}
|
|
|
|
// not an actual light, just handles the attach/detach
|
|
Class DemolitionistSelfLight : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
}
|
|
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()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || (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.viewz-target.pos.z),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:
|
|
CCD1 ABCDEFGHIJKLMNOP 2;
|
|
Loop;
|
|
Missile:
|
|
CCD1 A 2;
|
|
CCD2 ABCDEF 2;
|
|
Goto See;
|
|
Death:
|
|
CCD1 A 2;
|
|
CCD3 ABCDEFGHIJKLMNOP 2;
|
|
CCD3 Q -1;
|
|
Stop;
|
|
}
|
|
}
|