flak_m/zscript/utcommon.zsc
Marisa Kirisame cad3f9d1b9 Language adjustments.
Separation of See state for playerclasses so player selection doesn't crap out.
2019-09-01 22:09:31 +02:00

2777 lines
76 KiB
Text

Class UTPlayer : DoomPlayer
{
bool lastground;
int lastwaterlevel;
int lastgroundtic;
double lastvelz, prevvelz;
transient CVar footsteps;
Vector2 acceleration;
Vector3 acceleration3;
int last_fm, last_sm;
int last_fm_tap, last_sm_tap;
int last_tap_fm, last_tap_sm;
int last_jump_held;
int tempslide;
double ssup;
int corpsetime;
bool headless, leglessR, leglessL, armlessR, armlessL, torsoless;
// these are gibber hints to disable spawning some parts
// headless: missing head
// leglessR: missing right leg
// leglessL: mising left leg
// armlessR: missing right arm
// armlessL: mising left arm
// torsoless: missing torso
int dolltype, voicetype;
Property DollType : dolltype;
Property VoiceType : voicetype;
enum EDollType
{
DOLL_Male,
DOLL_Female,
DOLL_Boss
};
enum EVoiceType
{
VOICE_None,
VOICE_MaleOne,
VOICE_MaleTwo,
VOICE_FemaleOne,
VOICE_FemaleTwo,
VOICE_Boss
};
const groundspeed = 400.;
const swimspeed = 200.;
const baseaccelrate = 2048.;
const walkfactor = 0.3;
const utaircontrol = 0.35;
const swimspeed_doomish = 400.;
const groundspeed_doomish = 600.;
const dodgez = 210.;
const utjumpz = 325.;
const groundfriction = 8.;
const fluidfriction = 1.2;
const terminalvelocity = 2500.;
const slantnormal = 0.7; // slope sliding will have to be handled eventually, but currently due to how much is hardcoded this is impossible
Default
{
Player.StartItem "Enforcer";
Player.StartItem "ImpactHammer";
Player.StartItem "MiniAmmo", 30;
Player.DamageScreenColor "FF 00 00";
Player.ViewHeight 46;
Player.GruntSpeed 20;
+NOMENU;
+DONTTRANSLATE;
UTPlayer.DollType DOLL_Male;
UTPlayer.VoiceType VOICE_None;
}
// Have to modify the give cheat to handle UT armor
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 ( name ~== "health" )
{
if ( amount > 0 )
{
health += amount;
player.health = health;
}
else player.health = health = GetMaxHealth(true);
}
if ( giveall || (name ~== "backpack") )
{
// Select the correct type of backpack based on the game
let type = (class<Inventory>)(gameinfo.backpacktype);
if ( type ) GiveInventory(type,1,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 ( (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") )
{
// Doom Tournament just gives the player a shield belt and maximum bonuses
let belt = Inventory(Spawn("UTShieldBelt"));
if ( !belt.CallTryPickup(self) ) belt.Destroy();
let bonus = Inventory(Spawn("UTArmorBonus"));
bonus.Amount = bonus.MaxAmount;
if ( !bonus.CallTryPickup(self) ) bonus.Destroy();
level.total_items -= 2; // spawning them in raises item count
if ( !giveall ) return;
}
if ( giveall || (name ~== "keys") )
{
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
if ( !(AllActorClasses[i] is "Key") ) continue;
let keyitem = GetDefaultByType(AllActorClasses[i]);
if ( keyitem.special1 )
{
let item = Inventory(Spawn(AllActorClasses[i]));
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 ( !(gameinfo.gametype&GAME_Raven)
&& ((type is "UTActivatable") || (type is "UTActivatableHealth")) )
continue; // don't give these outside of Heretic/Hexen
if ( def.Icon.isValid() && (def.MaxAmount > 1) &&
!(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") && !(type is "Armor"))
{
// 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 void Tick()
{
Super.Tick();
if ( !player || (player.mo != self) ) return;
if ( flak_utmovement )
{
bNOFRICTION = true;
bNOFRICTIONBOUNCE = true;
}
else
{
bNOFRICTION = false;
bNOFRICTIONBOUNCE = false;
}
if ( !footsteps ) footsteps = CVar.GetCVar('flak_footsteps',players[consoleplayer]);
if ( !footsteps.GetBool() || (Health <= 0) ) return;
double ang = level.time/(20*TICRATE/35.)*360.;
bool forcefootstep = false;
if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) )
{
player.jumptics = 0;
if ( lastvelz < -8 )
{
double vol = clamp((-lastvelz-8)*0.05,0.01,1.0);
if ( ((waterlevel > 0) || GetFloorTerrain().IsLiquid) && !bOnMobj ) A_PlaySound("ut/wetsplash",CHAN_AUTO,vol);
else A_PlaySound("*uland",CHAN_AUTO,vol);
PlayLanding();
}
else forcefootstep = true;
}
if ( tempslide )
{
tempslide = max(0,tempslide-1);
if ( !tempslide ) forcefootstep = true;
}
if ( forcefootstep || ((abs(sin(ang)) >= 1.0) && player.onground && lastground && (player.jumptics == 0) && (player.cmd.forwardmove || player.cmd.sidemove) && (waterlevel < 2)) )
{
double vol = abs(vel.xy.length())*0.03;
if ( forcefootstep ) vol = clamp(-lastvelz*0.05,0.01,1.0);
if ( (waterlevel > 0) || GetFloorTerrain().IsLiquid && !bOnMobj ) A_PlaySound("ut/playerfootstepwet",CHAN_5,vol);
else PlayFootstep(vol);
}
if ( (waterlevel >= 2) && (lastwaterlevel < 2) )
A_PlaySound("ut/wetsplash",CHAN_AUTO);
else if ( (waterlevel < 2) && (lastwaterlevel >= 2) )
A_PlaySound("ut/wetsurface",CHAN_AUTO);
lastground = player.onground;
lastvelz = prevvelz;
prevvelz = vel.z;
lastwaterlevel = waterlevel;
}
double FrictionToUnreal()
{
double fin = GetFriction();
if ( fin >= 1.0 ) return 0.0;
return 734.2969*fin*fin-1485.0868*fin+750.7899;
}
override void CalcHeight()
{
if ( !flak_utmovement || !player || (player.mo != self) )
{
Super.CalcHeight();
return;
}
double angle, bob;
bool still = false;
// no bobbing while:
// - using noclip2 (equivalent to unreal's ghost cheat)
// - flying
// - swimming
// - falling
if ( !bNoGravity && player.onground && (waterlevel < 2) )
{
player.bob = player.Vel dot player.Vel;
if ( player.bob == 0 ) still = true;
else
{
player.bob *= player.GetMoveBob();
if ( player.bob > MAXBOB ) player.bob = MAXBOB;
}
}
else
{
// this still doesn't help because fly bob is hardcoded
player.bob = 0;
}
double defaultviewheight = ViewHeight+player.crouchviewdelta;
if ( player.cheats&CF_NOVELOCITY )
{
player.viewz = pos.z+defaultviewheight;
if ( player.viewz > ceilingz-4 ) player.viewz = ceilingz-4;
return;
}
if ( still )
{
if ( player.health > 0 )
{
angle = Level.maptime/(120*TICRATE/35.)*360.;
bob = player.GetStillBob()*sin(angle);
}
else bob = 0;
}
else
{
angle = Level.maptime/(20*TICRATE/35.)*360.;
bob = player.bob*sin(angle)*((waterlevel>1)?0.25:0.5);
}
// move viewheight
if ( player.playerstate == PST_LIVE )
{
player.viewheight += player.deltaviewheight;
if ( player.viewheight > defaultviewheight )
{
player.viewheight = defaultviewheight;
player.deltaviewheight = 0;
}
else if ( player.viewheight < (defaultviewheight/2) )
{
player.viewheight = defaultviewheight/2;
if ( player.deltaviewheight <= 0 )
player.deltaviewheight = 1/65536.;
}
if ( player.deltaviewheight )
{
player.deltaviewheight += 0.25;
if ( !player.deltaviewheight )
player.deltaviewheight = 1/65536.;
}
}
if ( player.morphTics ) bob = 0;
player.viewz = pos.z+player.viewheight+(bob*clamp(ViewBob,0.,1.5)); // [SP] Allow DECORATE changes to view bobbing speed.
// handle smooth step down (hacky but looks ok)
player.viewz += ssup;
ssup = max(0,(ssup*0.7)-0.25);
if ( floorclip && (player.playerstate != PST_DEAD) && (pos.z <= floorz) )
player.viewz -= Floorclip;
if ( player.viewz > ceilingz-4 ) player.viewz = ceilingz-4;
if ( player.viewz < floorz+4 ) player.viewz = floorz+4;
}
override void MovePlayer()
{
if ( !flak_utmovement || !player || (player.mo != self) )
{
Super.MovePlayer();
return;
}
bNODROPOFF = false;
UserCmd cmd = player.cmd;
if ( player.turnticks )
{
player.turnticks--;
Angle += (180./TURN180_TICKS);
}
else Angle += cmd.yaw*(360./65536.);
player.onground = (pos.z <= floorz) || bOnMobj || bMBFBouncer || (player.cheats & CF_NOCLIP2);
// slant (aka steep slope) detection
// TODO make it 3d floor aware when the PR makes it in
if ( floorsector.floorplane.normal dot (0,0,1) < (46342./65536.) )
player.onground = false;
if ( player.onground ) lastgroundtic = gametic;
if ( !player.onground && !bNoGravity && (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);
player.onground = true;
}
double friction = FrictionToUnreal();
double fs = TweakSpeeds(1.0,0.0);
if ( !flak_doomspeed )
{
if ( cmd.buttons&BT_SPEED ) fs *= walkfactor;
}
else fs *= max(abs(cmd.forwardmove/12800.),abs(cmd.sidemove/10240.));
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
acceleration = rotatevector((cmd.forwardmove,-cmd.sidemove),angle);
double accelrate = baseaccelrate*fs;
Vector2 dodge = (0,0);
int fm = cmd.forwardmove;
int sm = cmd.sidemove;
if ( fm )
{
int clk = abs(gametic-last_fm_tap);
if ( (clk < flak_taptics) && (last_fm*fm == 0) && (last_tap_fm*fm>0) )
dodge += RotateVector((fm,0),angle).unit();
if ( !last_fm && (last_jump_held < gametic-1) )
{
last_fm_tap = gametic;
last_tap_fm = fm;
}
}
last_fm = fm;
if ( sm )
{
int clk = abs(gametic-last_sm_tap);
if ( (clk < flak_taptics) && (last_sm*sm == 0) && (last_tap_sm*sm>0) )
dodge += RotateVector((0,-sm),angle).unit();
if ( !last_sm && (last_jump_held < gametic-1) )
{
last_sm_tap = gametic;
last_tap_sm = sm;
}
}
last_sm = sm;
if ( !bNoGravity && player.onground && (waterlevel < 2) )
{
if ( flak_tapdodge && (dodge.length() > 0) && !tempslide )
{
if ( level.IsJumpingAllowed() )
{
if ( flak_doomspeed ) vel += dodge.unit()*(groundspeed_doomish*1.5)/TICRATE;
else vel += dodge.unit()*(groundspeed*1.5)/TICRATE;
vel.z += dodgez/TICRATE;
}
else
{
if ( flak_doomspeed ) vel += dodge.unit()*(groundspeed_doomish*2.0)/TICRATE;
else vel += dodge.unit()*(groundspeed*2.0)/TICRATE;
tempslide = 8;
}
bOnMobj = false;
if ( !(player.cheats&CF_PREDICTING) )
A_PlaySound("*jump",CHAN_BODY);
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
player.vel *= 0;
player.jumptics = -2;
}
else
{
if ( flak_nowalkdrop && !tempslide )
bNODROPOFF = ((acceleration.length() > double.epsilon) && (cmd.buttons&BT_SPEED));
// Hook in Unreal physics
Vector2 dir = (0,0);
if ( vel.xy.length() > double.epsilon ) dir = vel.xy.unit();
double doomfriction = clamp(GetFriction()/ORIG_FRICTION,0.0,1.0);
if ( tempslide ) friction *= 0;
if ( acceleration.length() <= double.epsilon )
{
Vector2 oldvel = vel.xy;
vel.xy = vel.xy - (2 * dir) * vel.xy.length() * friction/TICRATE;
if ( oldvel dot vel.xy < 0.0 ) vel.xy *= 0;
}
else
{
Vector2 acceldir = acceleration.unit();
acceleration = acceldir * Min(acceleration.length(), accelrate/TICRATE);
vel.xy = vel.xy - (dir - acceldir) * vel.xy.length() * friction/TICRATE;
}
vel.xy = vel.xy + acceleration/TICRATE;
double maxvel;
if ( flak_doomspeed ) maxvel = groundspeed_doomish/TICRATE;
else maxvel = groundspeed/TICRATE;
maxvel *= fs*doomfriction;
// TODO attempt to replicate walk on ice velocity increase glitch
if ( vel.xy.length() > maxvel ) vel.xy = vel.xy.unit()*maxvel;
if ( !(player.cheats & CF_PREDICTING) )
{
if ( acceleration.length() <= double.epsilon ) PlayIdle();
else PlayRunning();
}
if ( tempslide ) player.vel *= 0;
else player.vel = vel.xy;
}
}
else if ( !bNoGravity && (waterlevel < 2) )
{
// air acceleration when falling
float maxaccel = accelrate/TICRATE;
if ( vel.xy.length() < (40./TICRATE) )
maxaccel += (40.-vel.xy.length())/TICRATE;
if ( acceleration.length() > maxaccel )
acceleration = acceleration.unit()*maxaccel;
Vector2 dir = (0,0);
if ( vel.xy.length() > double.epsilon ) dir = vel.xy.unit();
if ( acceleration.length() > double.epsilon )
{
Vector2 acceldir = acceleration.unit();
acceleration = acceldir * Min(acceleration.length(), accelrate/TICRATE);
}
acceleration *= flak_doomaircontrol?level.aircontrol:utaircontrol;
double maxvel;
if ( flak_doomspeed ) maxvel = (groundspeed_doomish*fs)/TICRATE;
else maxvel = (groundspeed*fs)/TICRATE;
// if new velocity is higher than ground speed, steer but don't increase it
if ( (vel.xy+acceleration/TICRATE).length() > maxvel )
{
double vsiz = vel.xy.length();
vel.xy = (vel.xy+acceleration/TICRATE).unit()*vsiz;
}
else vel.xy = vel.xy+acceleration/TICRATE;
if ( vel.length() > terminalvelocity/TICRATE ) vel = vel.unit()*(terminalvelocity/TICRATE);
player.vel *= 0;
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) ) PlayIdle();
}
else if ( (bFly && bFlyCheat) || (player.cheats&CF_NOCLIP2) )
{
// fly cheat has infinite friction (player stops moving when movement keys are released)
Vector3 dir = (0,0,0);
if ( vel.length() > double.epsilon ) dir = vel.unit();
Vector3 x, y;
[x, y] = dt_CoordUtil.GetAxes(pitch,angle,0);
acceleration3 = x*player.cmd.forwardmove+y*player.cmd.sidemove;
if ( player.cmd.buttons&BT_JUMP ) acceleration3.z = 0x500;
else if ( player.cmd.buttons&BT_CROUCH ) acceleration3.z = -0x500;
if ( acceleration3.length() <= double.epsilon ) vel *= 0;
else
{
Vector3 acceldir = acceleration3.unit();
acceleration3 = acceldir*Min(acceleration3.length(),accelrate/TICRATE);
vel = vel-(dir-acceldir)*vel.length();
}
vel = vel+acceleration3/TICRATE;
double maxvel;
if ( flak_doomspeed ) maxvel = groundspeed_doomish/TICRATE;
else maxvel = groundspeed/TICRATE;
maxvel *= fs;
if ( vel.length() > maxvel ) vel = vel.unit()*maxvel;
player.vel *= 0;
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) ) PlayIdle();
}
else
{
// swimming is pretty much like ground movement, but with much reduced friction and lower speed
friction *= fluidfriction/groundfriction;
Vector3 dir = (0,0,0);
if ( vel.length() > double.epsilon ) dir = vel.unit();
double doomfriction = clamp(GetFriction()/ORIG_FRICTION,0.0,1.0);
Vector3 x, y;
[x, y] = dt_CoordUtil.GetAxes(pitch,angle,0);
acceleration3 = x*player.cmd.forwardmove+y*player.cmd.sidemove;
if ( player.cmd.buttons&BT_JUMP ) acceleration3.z = 0x500;
else if ( player.cmd.buttons&BT_CROUCH ) acceleration3.z = -0x500;
if ( acceleration3.length() <= double.epsilon )
{
Vector3 oldvel = vel;
vel = vel-(2*dir)*vel.length()*friction/TICRATE;
if ( oldvel dot vel < 0.0 ) vel *= 0;
}
else
{
Vector3 acceldir = acceleration3.unit();
acceleration3 = acceldir*Min(acceleration3.length(),accelrate/TICRATE);
vel = vel-(dir-acceldir)*vel.length()*friction/TICRATE;
}
vel = vel+acceleration3/TICRATE;
double maxvel;
if ( waterlevel < 2 ) // flying uses ground speed
{
if ( flak_doomspeed ) maxvel = groundspeed_doomish/TICRATE;
else maxvel = groundspeed/TICRATE;
}
else
{
if ( flak_doomspeed ) maxvel = swimspeed_doomish/TICRATE;
else maxvel = swimspeed/TICRATE;
}
maxvel *= fs*doomfriction;
if ( vel.length() > maxvel ) vel = vel.unit()*maxvel;
player.vel = vel.xy;
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) )
{
if ( acceleration3.length() <= double.epsilon ) PlayIdle();
else PlayRunning();
}
}
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
override void CheckCrouch( bool totallyfrozen )
{
if ( !flak_utmovement || !player || (player.mo != self) )
{
Super.CheckCrouch(totallyfrozen);
return;
}
if ( player.cmd.buttons&BT_JUMP ) player.cmd.buttons &= ~BT_CROUCH;
if ( CanCrouch() && (player.health > 0) && level.IsCrouchingAllowed() && player.onground ) // in UT you can't crouch unless you're on the ground
{
if ( !totallyfrozen )
{
int crouchdir = player.crouching;
if ( !crouchdir ) crouchdir = (player.cmd.buttons&BT_CROUCH)?-1:1;
else if ( player.cmd.buttons&BT_CROUCH ) player.crouching = 0;
if ( (crouchdir == 1) && (player.crouchfactor < 1) && (pos.Z+height < ceilingz) )
CrouchMove(1);
else if ( (crouchdir == -1) && (player.crouchfactor > 0.5 ))
CrouchMove(-1);
}
}
else player.Uncrouch();
player.crouchoffset = -(ViewHeight)*(1-player.crouchfactor);
}
override void CheckJump()
{
if ( !flak_utmovement || !player || (player.mo != self) )
{
Super.CheckJump();
return;
}
if ( player.cmd.buttons&BT_JUMP )
{
if ( player.crouchoffset ) player.crouching = 1;
else if ( level.IsJumpingAllowed() && player.onground && (player.jumpTics == 0) && (last_jump_held < gametic-1) && !bNoGravity && (waterlevel < 2) && !(player.cheats&(CF_FLY|CF_NOCLIP2)) )
{
double jumpvelz;
if ( flak_doomspeed ) jumpvelz = jumpz;
else jumpvelz = utjumpz/TICRATE;
double jumpfac = 0;
for ( let p = Inv; p != null; p = p.Inv )
{
let pp = PowerHighJump(p);
if ( !pp ) continue;
double f = pp.Strength;
if ( f > jumpfac ) jumpfac = f;
}
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
Vel.z += jumpvelz;
bOnMobj = false;
player.jumpTics = -1;
if ( !(player.cheats&CF_PREDICTING) )
A_PlaySound("*jump",CHAN_BODY);
}
last_jump_held = gametic;
}
if ( !player.onground || player.jumptics )
last_jump_held = gametic;
}
override void DeathThink()
{
Super.DeathThink();
if ( !flak_utmovement ) return;
// unreal physics while dead
double friction = FrictionToUnreal();
if ( !bNoGravity && player.onground && (waterlevel < 2) )
{
// Hook in Unreal physics
Vector2 dir = (0,0);
if ( vel.xy.length() > double.epsilon ) dir = vel.xy.unit();
double doomfriction = clamp(GetFriction()/ORIG_FRICTION,0.0,1.0);
Vector2 oldvel = vel.xy;
vel.xy = vel.xy - (2 * dir) * vel.xy.length() * friction/TICRATE;
if ( oldvel dot vel.xy < 0.0 ) vel.xy *= 0;
double maxvel;
if ( flak_doomspeed ) maxvel = groundspeed_doomish/TICRATE;
else maxvel = groundspeed/TICRATE;
maxvel *= doomfriction;
if ( vel.xy.length() > maxvel ) vel.xy = vel.xy.unit()*maxvel;
player.vel *= 0;
}
else if ( !bNoGravity && (waterlevel < 2) )
{
// air acceleration when falling
Vector2 dir = (0,0);
if ( vel.xy.length() > double.epsilon ) dir = vel.xy.unit();
double maxvel;
if ( flak_doomspeed ) maxvel = groundspeed_doomish/TICRATE;
else maxvel = groundspeed/TICRATE;
// if new velocity is higher than ground speed, steer but don't increase it
if ( vel.xy.length() > maxvel )
{
double vsiz = vel.xy.length();
vel.xy = vel.xy.unit()*vsiz;
}
if ( vel.length() > terminalvelocity/TICRATE ) vel = vel.unit()*(terminalvelocity/TICRATE);
player.vel *= 0;
}
else
{
// swimming is pretty much like ground movement, but with much reduced friction and lower speed
friction *= fluidfriction/groundfriction;
Vector3 dir = (0,0,0);
if ( vel.length() > double.epsilon ) dir = vel.unit();
double doomfriction = clamp(GetFriction()/ORIG_FRICTION,0.0,1.0);
Vector3 oldvel = vel;
vel = vel-(2*dir)*vel.length()*friction/TICRATE;
if ( oldvel dot vel < 0.0 ) vel *= 0;
double maxvel;
if ( flak_doomspeed ) maxvel = swimspeed_doomish/TICRATE;
else maxvel = swimspeed/TICRATE;
maxvel *= doomfriction;
if ( vel.length() > maxvel ) vel = vel.unit()*maxvel;
player.vel *= 0;
}
}
virtual void PlayFootstep( double vol )
{
A_PlaySound("ut/playerfootstep",CHAN_5,vol);
}
override void PlayIdle()
{
if ( (player && (player.mo == self)) && (player.crouchdir == -1) )
{
// Crouching
if ( !InStateSequence(CurState,FindState("SpawnCrouch")) )
SetStateLabel("SpawnCrouch");
}
else if ( !bNoGravity && player.onground && (waterlevel < 2) )
{
// Ground
if ( (player && (player.mo == self)) && player.cmd.yaw )
{
if ( InStateSequence(CurState,FindState("See2"))
|| InStateSequence(CurState,FindState("SeeAir"))
|| InStateSequence(CurState,FindState("SeeSwim"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("SpawnAir"))
|| InStateSequence(CurState,FindState("SpawnSwim"))
|| InStateSequence(CurState,FindState("SpawnCrouch"))
|| InStateSequence(CurState,FindState("Spawn")+1) )
SetStateLabel("Turn");
}
else
{
if ( InStateSequence(CurState,FindState("See2"))
|| InStateSequence(CurState,FindState("SeeAir"))
|| InStateSequence(CurState,FindState("SeeSwim"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("SpawnAir"))
|| InStateSequence(CurState,FindState("SpawnSwim"))
|| InStateSequence(CurState,FindState("SpawnCrouch")) )
SetStateLabel("Spawn");
}
}
else if ( !bNoGravity && (waterlevel < 2) )
{
// Falling
if ( InStateSequence(CurState,FindState("See2"))
|| InStateSequence(CurState,FindState("SeeAir"))
|| InStateSequence(CurState,FindState("SeeSwim"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("SpawnSwim"))
|| InStateSequence(CurState,FindState("SpawnCrouch"))
|| InStateSequence(CurState,FindState("Turn")) )
SetStateLabel("SpawnAir");
}
else
{
// Swimming
if ( InStateSequence(CurState,FindState("See2"))
|| InStateSequence(CurState,FindState("SeeAir"))
|| InStateSequence(CurState,FindState("SeeSwim"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("SpawnAir"))
|| InStateSequence(CurState,FindState("SpawnCrouch"))
|| InStateSequence(CurState,FindState("Turn")) )
SetStateLabel("SpawnSwim");
}
}
override void PlayRunning()
{
if ( (player && (player.mo == self)) && (player.crouchdir == -1) )
{
// Crouching
if ( !InStateSequence(CurState,FindState("SeeCrouch")) )
SetStateLabel("SeeCrouch");
}
else if ( !bNoGravity && player.onground && (waterlevel < 2) )
{
// Ground
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("SpawnAir"))
|| InStateSequence(CurState,FindState("SpawnSwim"))
|| InStateSequence(CurState,FindState("SpawnCrouch"))
|| InStateSequence(CurState,FindState("SeeAir"))
|| InStateSequence(CurState,FindState("SeeSwim"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("Turn")) )
SetStateLabel("See2");
}
else if ( !bNoGravity && (waterlevel < 2) )
{
// Falling
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("SpawnAir"))
|| InStateSequence(CurState,FindState("SpawnSwim"))
|| InStateSequence(CurState,FindState("SpawnCrouch"))
|| InStateSequence(CurState,FindState("See2"))
|| InStateSequence(CurState,FindState("SeeSwim"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("Turn")) )
SetStateLabel("SeeAir");
}
else
{
// Swimming
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("SpawnAir"))
|| InStateSequence(CurState,FindState("SpawnSwim"))
|| InStateSequence(CurState,FindState("SpawnCrouch"))
|| InStateSequence(CurState,FindState("See2"))
|| InStateSequence(CurState,FindState("SeeAir"))
|| InStateSequence(CurState,FindState("SeeCrouch"))
|| InStateSequence(CurState,FindState("Turn")) )
SetStateLabel("SeeSwim");
}
}
override void PlayAttacking()
{
// no animation if crouched
if ( (player && (player.mo == self)) && (player.crouchdir == -1) ) return;
// check weapon type
if ( (player.ReadyWeapon is 'SniperRifle') && (player.buttons&BT_ALTATTACK) )
return;
if ( ((player.ReadyWeapon is 'BioRifle') && (player.buttons&BT_ALTATTACK))
|| (player.ReadyWeapon is 'UTRocketLauncher') )
{
if ( !InStateSequence(CurState,FindState("MissileRepStill")) )
SetStateLabel("MissileRepStill");
}
else if ( ((player.ReadyWeapon is 'ImpactHammer') && (player.buttons&BT_ATTACK))
|| ((player.ReadyWeapon is 'UTChainsaw') && (player.buttons&BT_ATTACK))
|| (player.ReadyWeapon is 'PulseGun')
|| (player.ReadyWeapon is 'Minigun') )
{
if ( !InStateSequence(CurState,FindState("MissileRep")) )
SetStateLabel("MissileRep");
}
else SetStateLabel("Missile");
}
override void PlayAttacking2()
{
PlayAttacking();
}
virtual void PlayAttacking3()
{
if ( (player && (player.mo == self)) && (player.crouchdir == -1) ) return;
SetStateLabel("Missile");
}
virtual void PlayReloading()
{
if ( (player && (player.mo == self)) && (player.crouchdir == -1) ) return;
SetStateLabel("Reload");
}
virtual void PlayLanding()
{
if ( (player && (player.mo == self)) && (player.crouchdir == -1) ) return;
SetStateLabel("Landing");
}
void A_HeadPop()
{
headless = true;
Class<UTHead> hclass = "UTHeadMale";
if ( DollType == DOLL_Boss ) hclass = "UTHeadBoss";
else if ( DollType == DOLL_Female ) hclass = "UTHeadFemale";
let h = UTHead(Spawn(hclass,Vec3Offset(0,0,viewheight)));
if ( player )
{
h.headowner = player;
player.camera = h;
}
double ang = FRandom[Blod](0,360);
double pt = FRandom[Blod](-90,-30);
Vector3 dir = (cos(pt)*cos(ang),cos(pt)*sin(ang),sin(-pt));
h.vel = vel*0.6+dir*FRandom[Blod](8.0,12.0);
}
void A_GibMe()
{
let a = Actor.Spawn("UTPlayerGibber",pos);
a.vel = vel;
a.Scale = Scale;
a.A_SetSize(radius,height);
UTGibber(a).Gibbed = self;
}
void A_DMFade()
{
if ( !deathmatch || player ) return;
corpsetime++;
if ( corpsetime > 350 ) A_FadeOut(0.03);
}
override double GetDeathHeight()
{
// no height reduction while still being zapped
if ( DamageType == 'Zapped' ) return height;
return Super.GetDeathHeight();
}
States
{
Spawn:
#### # 5;
PLAY ABCDEFG 8;
Goto Spawn+1;
Turn:
#### # 6;
PLYT A 0 A_JumpIf(!player||!player.cmd.yaw,"Spawn");
PLYT A 6;
PLYT A 0 A_JumpIf(!player||!player.cmd.yaw,"Spawn");
PLYT A 6;
PLYT B 0 A_JumpIf(!player||!player.cmd.yaw,"Spawn");
PLYT B 6;
PLYT B 0 A_JumpIf(!player||!player.cmd.yaw,"Spawn");
PLYT B 6;
Goto Turn+1;
SpawnAir:
#### # 5;
PLYA A 50;
PLYL A 50;
Goto SpawnAir+1;
SpawnSwim:
#### # 5;
PLYS ABCDEFG 6;
Goto SpawnSwim+1;
SpawnCrouch:
#### # 5;
PLYC A -1;
Wait;
// See state needs to be defined on each subclass for the selection menu
See2:
#### # 5;
PLAY HIJKL 4;
Goto See2+1;
SeeAir:
#### # 5;
PLYA A 50;
PLYL A 50;
Goto SeeAir+1;
SeeSwim:
#### # 5;
PLYS HIJKL 4;
Goto SeeSwim+1;
SeeCrouch:
#### # 5;
PLYC ABCDE 4;
Goto SeeCrouch+1;
Missile:
#### # 2;
PLAY MNOPQA 3;
Goto Spawn;
MissileRep:
#### # 2;
PLAY R 2;
PLAY S 0 A_JumpIf(!player||!(player.buttons&(BT_ATTACK|BT_ALTATTACK)),"Spawn");
PLAY S 2;
PLAY T 0 A_JumpIf(!player||!(player.buttons&(BT_ATTACK|BT_ALTATTACK)),"Spawn");
PLAY T 2;
PLAY U 0 A_JumpIf(!player||!(player.buttons&(BT_ATTACK|BT_ALTATTACK)),"Spawn");
PLAY U 2;
PLAY V 0 A_JumpIf(!player||!(player.buttons&(BT_ATTACK|BT_ALTATTACK)),"Spawn");
PLAY V 2;
PLAY R 0 A_JumpIf(!player||!(player.buttons&(BT_ATTACK|BT_ALTATTACK)),"Spawn");
Goto MissileRep+1;
MissileRepStill:
#### # 2;
PLAY R 2;
PLAY R 0 A_JumpIf(!player||!(player.buttons&(BT_ATTACK|BT_ALTATTACK)),"Spawn");
Goto MissileRepStill+1;
Reload:
#### # 3;
PLYR ABCDEFGH 4;
Goto Spawn;
Landing:
#### # 4;
PLYL A 3;
Goto Spawn;
Pain:
#### # 0 A_Jump(256,1,3,5,7);
#### # 2;
PLAY W 3 A_Pain();
Goto Spawn;
#### # 2;
PLAY Y 3 A_Pain();
Goto Spawn;
#### # 2;
PLAY Z 3 A_Pain();
Goto Spawn;
Pain.Decapitated:
#### # 2;
PLAY X 3 A_Pain();
Goto Spawn;
Death.Decapitated:
#### # 0 A_HeadPop();
PLD4 A 3 A_PlayerScream();
PLD4 B 3 A_NoBlocking();
PLD4 CDEFGHIJKLMNO 3;
PLD4 P 1 A_DMFade();
Wait;
Death:
#### # 0 A_JumpIf(vel.length()>20,"Death11");
#### # 0 A_JumpIf(vel.length()>10,"Death1");
#### # 0 A_Jump(256,"Death2","Death3","Death7","Death8");
Death11:
#### # 3;
PD11 A 3 A_PlayerScream();
PD11 B 3 A_NoBlocking();
PD11 CDEFGHIJKLMNOPQ 3;
PD11 R 1 A_DMFade();
Wait;
Death.Zapped:
#### # 3;
PLD9 A 3 A_PlayerScream();
PLD9 B 3 A_NoBlocking();
PLD9 CDEFGHIJKLMNOPQRST 2;
PD9B A 2 A_SetSize(-1,height*0.25); // reduce now
PD9B BCDEFGHI 2;
PD9B J 1 A_DMFade();
Wait;
Death8:
#### # 3;
PLD8 A 3 A_PlayerScream();
PLD8 B 3 A_NoBlocking();
PLD8 CDEFGHIJKLMNOPQ 3;
PLD8 R 1 A_DMFade();
Wait;
Death7:
#### # 3;
PLD7 A 3 A_PlayerScream();
PLD7 B 3 A_NoBlocking();
PLD7 CDEFGHIJKLMNOPQRST 3;
PLD7 U 1 A_DMFade();
Wait;
Death3:
#### # 3;
PLD3 A 3 A_PlayerScream();
PLD3 B 3 A_NoBlocking();
PLD3 CDEFGHIJKL 3;
PLD3 M 1 A_DMFade();
Wait;
Death2:
#### # 3;
PLD2 A 3 A_PlayerScream();
PLD2 B 3 A_NoBlocking();
PLD2 CDEFGHIJKLMNO 3;
PLD2 P 1 A_DMFade();
Wait;
Death1:
#### # 3;
PLD1 A 3 A_PlayerScream();
PLD1 B 3 A_NoBlocking();
PLD1 CDEFGHIJKL 3;
PLD1 M 1 A_DMFade();
Wait;
XDeath:
TNT1 A 350
{
A_XScream();
A_NoBlocking();
A_GibMe();
}
TNT1 A 1 A_CheckPlayerDone();
Wait;
Taunt1:
#### # 5;
PLT1 ABCDEFGHIJKLMNOPQRST 3;
Goto Spawn;
Taunt2:
#### # 5;
PLT2 ABCDEFGHIJKLMNOPQR 4;
PLT2 R 8;
PLAY A 3;
Goto Spawn;
Taunt3:
#### # 5;
PLT3 ABCDEFGHIJKLMNO 3;
Goto Spawn;
Taunt4:
#### # 5;
PLT4 ABCDEFGHIJKLMNO 3;
Goto Spawn;
}
}
// these only exist for sound
// female classes have identical sounds, so they use the same soundclass here
Class UTPlayerTMale1 : UTPlayer
{
Default
{
Player.SoundClass "tmale1";
Player.DisplayName "$N_TMALE1";
Player.Portrait "Blake";
UTPlayer.VoiceType VOICE_MaleOne;
-NOMENU;
}
}
Class UTPlayerTMale2 : UTPlayer
{
Default
{
Player.SoundClass "tmale2";
Player.DisplayName "$N_TMALE2";
Player.Portrait "Brock";
UTPlayer.VoiceType VOICE_MaleTwo;
-NOMENU;
}
}
Class UTPlayerTFemale : UTPlayer
{
Default
{
UTPlayer.DollType DOLL_Female;
}
void A_LegPop()
{
leglessR = true;
let a = Actor.Spawn("UTFemaleLegGibber",pos);
a.vel = vel;
a.Scale = Scale;
a.A_SetSize(radius,height);
UTGibber(a).Gibbed = self;
}
States
{
Reload:
#### # 3;
PLYR ABCDEF 4;
Goto Spawn;
Taunt1:
#### # 5;
PLT1 ABCDEFGHIJKLMNO 3;
Goto Spawn;
Taunt2:
#### # 5;
PLT2 ABCDEFGHIJ 6;
PLAY A 3;
Goto Spawn;
Death.Decapitated:
#### # 0 A_HeadPop();
PLD6 A 3 A_PlayerScream();
PLD6 B 3 A_NoBlocking();
PLD6 CDEFGHIJ 3;
PLD6 K 1 A_DMFade();
Wait;
Death.Zapped:
#### # 3;
PLD9 A 3 A_PlayerScream();
PLD9 B 3 A_NoBlocking();
PLD9 CDEFGHIJKLMNOPQRST 2;
PD9B A 2 A_SetSize(-1,height*0.25); // reduce now
PD9B BCDEFGHIJ 2;
PD9B K 1 A_DMFade();
Wait;
Death:
#### # 0 A_JumpIf(vel.length()>20,"Death5");
#### # 0 A_JumpIf(vel.length()>10,"Death2");
#### # 0 A_Jump(256,"Death1","Death3","Death4","Death7");
Death7:
#### # 3;
PLD7 A 3 A_PlayerScream();
PLD7 B 3 A_NoBlocking();
PLD7 CDEFGHIJ 3;
PLD7 K 1 A_DMFade();
Wait;
Death5:
#### # 0 A_LegPop();
PLD5 A 3 A_PlayerScream();
PLD5 B 3 A_NoBlocking();
PLD5 CDEFGHIJKL 3;
PLD5 M 1 A_DMFade();
Wait;
Death4:
#### # 3;
PLD4 A 3 A_PlayerScream();
PLD4 B 3 A_NoBlocking();
PLD4 CDEFGHIJKL 3;
PLD4 M 1 A_DMFade();
Wait;
Death3:
#### # 3;
PLD3 A 3 A_PlayerScream();
PLD3 B 3 A_NoBlocking();
PLD3 CDEFGHIJKLMNO 3;
PLD3 P 1 A_DMFade();
Wait;
Death2:
#### # 3;
PLD2 A 3 A_PlayerScream();
PLD2 B 3 A_NoBlocking();
PLD2 CDEFGHIJKLMNOPQ 3;
PLD2 R 1 A_DMFade();
Wait;
Death1:
#### # 3;
PLD1 A 3 A_PlayerScream();
PLD1 B 3 A_NoBlocking();
PLD1 CDEFGHIJKLMNOPQRSTUV 3;
PLD1 W 1 A_DMFade();
Wait;
}
}
Class UTPlayerTFemale1 : UTPlayerTFemale
{
Default
{
Player.SoundClass "tfemale";
Player.DisplayName "$N_TFEMALE1";
Player.Portrait "Ivana";
UTPlayer.VoiceType VOICE_FemaleOne;
-NOMENU;
}
}
Class UTPlayerTFemale2 : UTPlayerTFemale
{
Default
{
Player.SoundClass "tfemale";
Player.DisplayName "$N_TFEMALE2";
Player.Portrait "Lauren";
UTPlayer.VoiceType VOICE_FemaleTwo;
-NOMENU;
}
}
Class UTPlayerTBoss : UTPlayer
{
transient CVar bossfootsteps;
Default
{
Player.SoundClass "tboss";
Player.DisplayName "$N_TBOSS";
Player.Portrait "Xan";
UTPlayer.DollType DOLL_Boss;
UTPlayer.VoiceType VOICE_Boss;
// should have NOBLOOD, but Xan did bleed in vanilla UT so...
// (this is what gave birth to the theory that Xan was actually Jerl Liandri himself)
-NOMENU;
}
override void PlayFootstep( double vol )
{
if ( !bossfootsteps ) bossfootsteps = CVar.GetCVar('flak_bossfootsteps',players[consoleplayer]);
if ( bossfootsteps.GetBool() ) A_PlaySound("ut/bossfootstep",CHAN_5,vol);
else Super.PlayFootstep(vol);
}
}
Class UTWeapon : Weapon
{
int DropAmmo;
bool bExtraPickup;
Property DropAmmo: DropAmmo;
// Drawstuffs under HUD
virtual ui void PreRender( double lbottom ) {}
// Drawstuffs over HUD
virtual ui void PostRender( double lbottom ) {}
// Future preparations for scripted textures
virtual ui void RenderOverlay( RenderEvent e ) {}
override Inventory CreateTossable( int amt )
{
if ( Ammo1 && (Ammo1.Amount <= 0) ) return null;
Inventory d = Super.CreateTossable(amt);
if ( d && (d.GetClass() == GetClass()) )
{
d.SetState(d.ResolveState("Spawn")+1);
d.bALWAYSPICKUP = true;
}
return d;
}
override bool SpecialDropAction( Actor dropper )
{
SetState(ResolveState("Spawn")+1);
bALWAYSPICKUP = true;
return false;
}
override void Tick()
{
Super.Tick();
// don't slide on floor when dropped
if ( CurState == ResolveState("Spawn")+1 )
{
if ( pos.z <= floorz )
vel.xy *= 0;
}
if ( !Owner || !Owner.player || (Owner.player.ReadyWeapon != self) ) return;
Owner.player.WeaponState |= WF_WEAPONBOBBING; // UT weapons always bob
}
virtual void FireEffect()
{
let amp = DamageAmplifier(Owner.FindInventory("DamageAmplifier",true));
if ( amp ) amp.FireEffect();
}
override void OwnerDied()
{
Super.OwnerDied();
A_ClearRefire();
}
override bool HandlePickup( Inventory item )
{
if (item.GetClass() == GetClass())
{
if ( Weapon(item).PickupForAmmo(self) )
item.bPickupGood = true;
if ( (MaxAmount > 1) || bALWAYSPICKUP )
return Inventory.HandlePickup(item);
return true;
}
return false;
}
override bool ShouldStay()
{
if ( ((multiplayer && (!deathmatch && !alwaysapplydmflags)) || sv_weaponstay) && !bDropped )
return (!bExtraPickup && !bALWAYSPICKUP);
return false;
}
override bool TryPickup( in out Actor toucher )
{
if ( !bExtraPickup ) bExtraPickup = ((MaxAmount > 1) && (toucher.CountInv(GetClass()) < MaxAmount));
return Super.TryPickup(toucher);
}
// Whole chain of function rewrites because of some stupid hardcoded deathmatch ammo multiplier
override bool TryPickupRestricted( in out Actor toucher )
{
if ( ShouldStay() ) return false;
bExtraPickup = false;
bool gaveSome = !!(NonIdioticAddAmmo(toucher,AmmoType1,AmmoGive1));
gaveSome |= !!(NonIdioticAddAmmo(toucher,AmmoType2,AmmoGive2));
if ( gaveSome ) GoAwayAndDie();
return gaveSome;
}
override void AttachToOwner( Actor other )
{
bExtraPickup = false;
Inventory.AttachToOwner(other);
Ammo1 = NonIdioticAddAmmo(Owner,AmmoType1,AmmoGive1);
Ammo2 = NonIdioticAddAmmo(Owner,AmmoType2,AmmoGive2);
SisterWeapon = AddWeapon(SisterWeaponType);
if ( Owner.player )
{
if ( !Owner.player.GetNeverSwitch() && !bNo_Auto_Switch )
Owner.player.PendingWeapon = self;
if ( Owner.player.mo == players[consoleplayer].camera )
StatusBar.ReceivedWeapon(self);
}
GivenAsMorphWeapon = false;
}
// rewrite of AddAmmo without stupid hardcoded 2.5x ammo multiplier
protected Ammo NonIdioticAddAmmo( Actor other, Class<Ammo> ammotype, int amount )
{
if ( !ammotype ) return null;
Ammo ammoitem;
if ( !bIgnoreSkill ) amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor));
ammoitem = Ammo(other.FindInventory(ammotype));
if ( !ammoitem )
{
ammoitem = Ammo(Spawn(ammotype));
ammoitem.Amount = min(amount,ammoitem.MaxAmount);
ammoitem.AttachToOwner(other);
}
else if ( ammoitem.Amount < ammoitem.MaxAmount )
{
ammoitem.Amount += amount;
if ( ammoitem.Amount > ammoitem.MaxAmount )
ammoitem.Amount = ammoitem.MaxAmount;
}
return ammoitem;
}
override void OnDrop( Actor dropper )
{
Vector2 hofs = RotateVector((dropper.radius,0),dropper.angle);
SetOrigin(dropper.Vec3Offset(hofs.x,hofs.y,dropper.height*0.5),false);
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(dropper.pitch,dropper.angle,dropper.roll);
vel = x*12.0;
vel.z += 4.0;
angle = dropper.angle;
pitch = 0;
roll = 0;
}
Default
{
Weapon.BobStyle "Smooth";
Weapon.BobSpeed 1.5;
Weapon.BobRangeX 0.2;
Weapon.BobRangeY 0.4;
Weapon.YAdjust 0;
+WEAPON.NOALERT;
}
}
Class UTTeleportLight : DynamicLight
{
Default
{
DynamicLight.Type "Point";
Args 128,160,255,80;
}
override void Tick()
{
Super.Tick();
if ( alpha <= 0 )
{
Destroy();
return;
}
args[LIGHT_RED] = int(128*alpha);
args[LIGHT_GREEN] = int(160*alpha);
args[LIGHT_BLUE] = int(255*alpha);
args[LIGHT_INTENSITY] = Random[Tele](10,14)*8;
alpha -= 1./35;
}
}
Class UTItemLight : DynamicLight
{
Default
{
DynamicLight.Type "Point";
Args 255,224,160,48;
}
override void Tick()
{
Super.Tick();
if ( alpha <= 0 )
{
Destroy();
return;
}
args[LIGHT_RED] = int(255*alpha);
args[LIGHT_GREEN] = int(224*alpha);
args[LIGHT_BLUE] = int(160*alpha);
args[LIGHT_INTENSITY] = Random[Tele](6,8)*8;
alpha -= 3./35;
}
}
Class UTTeleportFog : Actor
{
Default
{
+NOBLOCKMAP;
+NOTELEPORT;
+NOGRAVITY;
RenderStyle "Add";
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
Spawn("UTTeleportLight",pos+(0,0,16));
A_PlaySound ("misc/teleport");
}
States
{
Spawn:
TELE ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 Bright A_FadeOut(1./35);
TEL2 ABCDEFGHI 1 Bright A_FadeOut(1./35);
Stop;
}
}
Class UTItemFog : Actor
{
Default
{
+NOBLOCKMAP;
+NOTELEPORT;
+NOGRAVITY;
RenderStyle "Add";
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
Spawn("UTItemLight",pos+(0,0,16));
}
States
{
Spawn:
TNT1 A 1;
Stop;
}
}
Class UTSpark : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+MISSILE;
+MOVEWITHSECTOR;
+THRUACTORS;
+NOTELEPORT;
+DONTSPLASH;
BounceType "Doom";
BounceFactor 0.4;
Gravity 0.2;
Scale 0.05;
}
override void Tick()
{
Super.Tick();
if ( waterlevel > 0 )
{
let b = Spawn("UTBubble",pos);
b.vel = vel;
b.scale *= 0.3;
Destroy();
}
}
States
{
Spawn:
SPRK A 1 Bright A_FadeOut(0.01);
Wait;
Death:
SPRK A 1 Bright A_FadeOut(0.05);
Wait;
}
}
Class UTViewSpark : UTSpark
{
Default
{
+NOCLIP;
-MISSILE;
BounceType "None";
}
Vector3 ofs, vvel;
override void Tick()
{
Actor.Tick();
if ( !target || !target.player )
{
Destroy();
return;
}
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
Vector3 origin = x*ofs.x+y*ofs.y+z*ofs.z+(0,0,target.player.viewz);
SetOrigin(target.Vec2OffsetZ(origin.x,origin.y,origin.z),true);
bInvisible = (players[consoleplayer].camera != target);
if ( isFrozen() ) return;
ofs += vvel;
vvel.z -= 0.1;
scale *= 0.8;
if ( (scale.x <= 0.01) || (waterlevel > 0) ) Destroy();
}
}
Class UTChip : Actor
{
int deadtimer;
double rollvel, anglevel, pitchvel;
Default
{
Radius 2;
Height 2;
+NOBLOCKMAP;
+MISSILE;
+MOVEWITHSECTOR;
+THRUACTORS;
+NOTELEPORT;
+DONTSPLASH;
+INTERPOLATEANGLES;
BounceType "Doom";
BounceFactor 0.3;
Gravity 0.35;
Scale 0.2;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
deadtimer = 0;
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
frame = Random[Junk](0,3);
scale *= Frandom[Junk](0.8,1.2);
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
if ( InStateSequence(CurState,ResolveState("Death")) )
{
deadtimer++;
if ( deadtimer > 300 ) A_FadeOut(0.05);
return;
}
}
States
{
Spawn:
CHIP # 1
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
Loop;
Bounce:
CHIP # 0
{
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
}
Goto Spawn;
Death:
CHIP # -1;
Stop;
Dummy:
CHIP ABCD -1;
Stop;
}
}
Class UTBubble : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+NOTELEPORT;
Scale 0.05;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
double ang, pt;
scale *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
if ( waterlevel <= 0 ) Destroy();
SetState(ResolveState("Spawn")+Random[Puff](0,2));
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
vel *= 0.96;
vel.z += 0.05;
if ( (waterlevel <= 0) || !Random[Puff](0,100) ) Destroy();
}
States
{
Spawn:
BUBL ABC -1;
Stop;
}
}
Class UTSmoke : Actor
{
Default
{
RenderStyle "Shaded";
StencilColor "FFFFFF";
Radius 2;
Height 2;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+THRUACTORS;
+NOTELEPORT;
+CANBOUNCEWATER;
-BOUNCEAUTOOFF;
BounceType "Hexen";
BounceFactor 1.0;
WallBounceFactor 1.0;
Scale 0.5;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
double ang, pt;
scale *= FRandom[Puff](0.5,1.5);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
vel *= 0.96;
vel.z += 0.01;
A_FadeOut(1/32.);
if ( waterlevel > 0 )
{
let b = Spawn("UTBubble",pos);
b.vel = vel;
Destroy();
}
}
States
{
Spawn:
TNT1 A 0 NoDelay A_Jump(255,"US1","US2","US3","US4","US5","US6","US7","US8","US9","US10");
Stop;
US1:
US1_ ABCDEFGHIJKLMNOP 2;
Stop;
US2:
US2_ ABCDEFGHIJKLMNOP 2;
Stop;
US3:
US3_ ABCDEFGHIJKLMNOP 2;
Stop;
US4:
US4_ ABCDEFGHIJKLMNO 2;
Stop;
US5:
US5_ ABCDEFGHIJKLMNO 2;
Stop;
US6:
US6_ ABCDEFGHIJKLMNOP 2;
Stop;
US7:
US7_ ABCDEFGHIJKLMNOP 2;
Stop;
US8:
US8_ ABCDEFGHIJKLMNOP 2;
Stop;
US9:
US9_ ABCDEFGHIJKLMNOP 2;
Stop;
US10:
US10 ABCDEFGHIJKLMNOP 2;
Stop;
}
}
Class UTSmallSmoke : UTSmoke
{
override void PostBeginPlay()
{
Actor.PostBeginPlay();
double ang, pt;
scale *= FRandom[Puff](0.1,0.3);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16);
}
}
Class UTViewSmoke : UTSmoke
{
Vector3 ofs, vvel;
override void PostBeginPlay()
{
Actor.PostBeginPlay();
double ang, pt;
scale *= FRandom[Puff](0.1,0.3);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vvel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16);
}
override void Tick()
{
Actor.Tick();
if ( !target || !target.player )
{
Destroy();
return;
}
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
Vector3 origin = x*ofs.x+y*ofs.y+z*ofs.z+(0,0,target.player.viewz);
SetOrigin(target.Vec2OffsetZ(origin.x,origin.y,origin.z),true);
bInvisible = (players[consoleplayer].camera != target);
if ( isFrozen() ) return;
ofs += vvel;
vvel *= 0.96;
vvel.z += 0.01;
A_FadeOut(1/32.);
if ( waterlevel > 0 ) Destroy();
}
}
Class UTStaticViewSmoke : UTViewSmoke
{
override void PostBeginPlay()
{
Actor.PostBeginPlay();
scale *= FRandom[Puff](0.1,0.3);
alpha *= FRandom[Puff](0.5,1.5);
}
}
Class UTRedSkull : RedSkull
{
Default
{
Tag "$T_REDSKULL";
Species "RedSkull";
Inventory.PickupMessage "$I_REDSKULL";
}
States
{
Spawn:
USKL A -1;
Stop;
}
}
Class UTGoldSkull : YellowSkull
{
Default
{
Tag "$T_GOLDSKULL";
Species "YellowSkull";
Inventory.PickupMessage "$I_GOLDSKULL";
}
States
{
Spawn:
USKL B -1;
Stop;
}
}
Class UTBlueSkull : BlueSkull
{
Default
{
Tag "$T_BLUESKULL";
Species "BlueSkull";
Inventory.PickupMessage "$I_BLUESKULL";
}
States
{
Spawn:
USKL C -1;
Stop;
}
}
Class UTRedKey : RedCard
{
Default
{
Tag "%T_REDKEY";
Species "RedCard";
Inventory.PickupMessage "$I_REDKEY";
}
States
{
Spawn:
UKEY A -1;
Stop;
}
}
Class UTGoldKey : YellowCard
{
Default
{
Tag "$T_GOLDKEY";
Species "YellowCard";
Inventory.PickupMessage "$I_GOLDKEY";
}
States
{
Spawn:
UKEY B -1;
Stop;
}
}
Class UTBlueKey : BlueCard
{
Default
{
Tag "$T_BLUEKEY";
Species "BlueCard";
Inventory.PickupMessage "$I_BLUEKEY";
}
States
{
Spawn:
UKEY C -1;
Stop;
}
}
Class ShredCorpseHitbox : Actor
{
int accdamage;
Vector3 lastvel;
bool wasonair;
Default
{
+NOGRAVITY;
-SOLID;
+DONTSPLASH;
+SHOOTABLE;
+NOTELEPORT;
Health int.max;
}
override void PostBeginPlay()
{
if ( !target )
{
Destroy();
return;
}
accdamage = target.Health;
A_SetSize(target.radius,target.height);
}
override void Tick()
{
Super.Tick();
if ( (!(target is 'UTPlayer') && !flak_corpsedamage) || !target || (target.Health > 0) || target.InStateSequence(target.CurState,target.FindState("XDeath",true)) )
{
Destroy();
return;
}
SetOrigin(target.pos,true);
A_SetSize(target.radius,target.height);
if ( target.pos.z > target.floorz ) wasonair = true;
else
{
if ( wasonair )
{
A_PlaySound("misc/corpsefall",CHAN_BODY,clamp(-lastvel.z*0.2,0.1,1.0));
if ( lastvel.z < -20 ) DamageMobj(null,null,int.max,'Falling');
}
wasonair = false;
}
lastvel = target.vel;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// somehow target can spontaneously stop existing while this is happening
if ( !target ) return 0;
if ( (target is 'UTPlayer') && (mod == 'Zapped') && (target.sprite == target.GetSpriteIndex('PLD9')) )
{
// keep the zapping action on
target.SetState(target.FindState("Death.Zapped")+Random[ZapMe](2,8));
damage /= 4;
// push the corpse
target.vel.xy += RotateVector((1,0),angle)*damage*0.1;
// keep it afloat
target.vel.z = max(0.1,target.vel.z+0.1);
}
accdamage -= damage;
int gibhealth = (target.GibHealth==int.min)?-target.SpawnHealth():target.GibHealth;
if ( accdamage < gibhealth )
{
// force gib (cheap ATM)
State gib = target.FindState("XDeath",true);
if ( !gib ) gib = target.FindState("Death.Extreme",true);
if ( gib ) target.SetState(gib);
Destroy();
}
return 0;
}
}
// imitates UE1 light type LT_TexturePaletteOnce
Class PaletteLight : DynamicLight
{
Color pal[256];
Default
{
Tag "Explosion";
DynamicLight.Type "Point";
Args 0,0,0,80;
ReactionTime 15;
}
private void UpdateLight()
{
int index = 255-((255*ReactionTime)/default.ReactionTime);
args[LIGHT_RED] = pal[index].r;
args[LIGHT_GREEN] = pal[index].g;
args[LIGHT_BLUE] = pal[index].b;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
int lump = Wads.CheckNumForFullname(String.Format("palettes/%s.pal",GetTag()));
String paldat = Wads.ReadLump(lump);
for ( int i=0; i<256; i++ )
{
pal[i].r = paldat.ByteAt(i*3);
pal[i].g = paldat.ByteAt(i*3+1);
pal[i].b = paldat.ByteAt(i*3+2);
}
UpdateLight();
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
A_CountDown();
UpdateLight();
}
}
Enum ESwingMode
{
SWING_Straight, // constant increment
SWING_Spring, // bounces back after a delay
};
Class Swinger : Thinker
{
Actor target;
Vector2 dir;
double inc, rmul;
int steps, mode, delay;
double str, tstr;
int cnt, cstate;
Enum ESwingerState
{
STATE_Initial,
STATE_Wait,
STATE_Return,
};
override void Tick()
{
if ( !target ) cstate = -1;
switch ( cstate )
{
case STATE_Initial:
target.A_SetAngle(target.angle+dir.x*str*flak_swingerstrength,SPF_INTERPOLATE);
target.A_SetPitch(target.pitch+dir.y*str*flak_swingerstrength,SPF_INTERPOLATE);
str += inc;
if ( ++cnt >= steps )
{
cnt = 0;
str = tstr/steps;
cstate = (mode==SWING_Straight)?(-1):(delay>0)?STATE_Wait:STATE_Return;
}
else tstr += str;
break;
case STATE_Wait:
if ( ++cnt >= delay )
{
cnt = 0;
cstate = STATE_Return;
}
break;
case STATE_Return:
target.A_SetAngle(target.angle-dir.x*(str/rmul)*flak_swingerstrength,SPF_INTERPOLATE);
target.A_SetPitch(target.pitch-dir.y*(str/rmul)*flak_swingerstrength,SPF_INTERPOLATE);
if ( ++cnt >= steps*rmul )
{
cnt = 0;
cstate = -1;
}
break;
default:
Destroy();
return;
}
}
}
Class GenericFlash : HUDMessageBase
{
Color col;
int duration;
double alpha;
Actor cam;
transient CVar str;
GenericFlash Setup( Actor camera, Color c, int d )
{
alpha = 1.0;
col = c;
duration = d;
cam = camera;
return self;
}
override bool Tick()
{
if ( duration > 0 ) alpha -= 1./duration;
return (alpha<=0)||(!cam);
}
override void Draw( int bottom, int visibility )
{
if ( automapactive || (visibility != BaseStatusBar.HUDMSGLayer_UnderHUD) ) return;
if ( cam && (players[consoleplayer].camera != cam) ) return;
if ( !str ) str = CVar.GetCVar('flak_flashstrength',players[consoleplayer]);
Screen.Dim(col,(col.a/255.)*alpha*str.GetFloat(),0,0,Screen.GetWidth(),Screen.GetHeight());
}
}
Class QueuedFlash
{
Color c;
int duration;
int tic;
Actor cam;
}
Class UTStaticHandler : StaticEventHandler
{
ui TextureID tex;
ui void StartMenu()
{
CVar protomenu = CVar.GetCVar('flak_protomenu',players[consoleplayer]);
if ( !protomenu ) return; // this can happen
int proto = protomenu.GetInt();
if ( proto )
{
tex = TexMan.CheckForTexture("protobg",TexMan.Type_Any);
if ( gamestate != GS_TITLELEVEL ) return;
if ( proto > 1 ) S_ChangeMusic("menu2");
else S_ChangeMusic("xyzdMenu");
}
else
{
tex = TexMan.CheckForTexture("finalbg",TexMan.Type_Any);
if ( gamestate != GS_TITLELEVEL ) return;
S_ChangeMusic("utmenu23");
}
}
override void ConsoleProcess( ConsoleEvent e )
{
if ( e.Name ~== "refreshmenu" ) StartMenu();
}
override void PostUiTick()
{
if ( gametic <= 0 ) StartMenu();
}
override void RenderOverlay( RenderEvent e )
{
// well this if sure is a long one
if ( players[consoleplayer].camera.player && players[consoleplayer].camera.player.ReadyWeapon && (players[consoleplayer].camera.player.ReadyWeapon is 'UTWeapon') )
UTWeapon(players[consoleplayer].camera.player.ReadyWeapon).RenderOverlay(e);
if ( !menuactive ) return;
if ( tex.IsNull() || !tex.IsValid() ) return;
if ( !CVar.GetCVar('flak_showmenu',players[consoleplayer]).GetBool() ) return;
Screen.Dim("Black",1.0,0,0,Screen.GetWidth(),Screen.GetHeight());
Screen.DrawTexture(tex,true,0,0,DTA_VirtualWidth,1024,DTA_VirtualHeight,768);
}
}
Class UTMainHandler : EventHandler
{
Array<QueuedFlash> flashes;
override void CheckReplacement( ReplaceEvent e )
{
if ( (e.Replacee == 'Chainsaw') || (e.Replacee == 'Gauntlets') )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'UTChainsaw';
else e.Replacement = 'Enforcer';
}
else if ( (e.Replacee == 'Fist') || (e.Replacee == 'Staff') ) e.Replacement = 'ImpactHammer';
else if ( (e.Replacee == 'Pistol') || (e.Replacee == 'GoldWand') ) e.Replacement = 'Enforcer';
else if ( (e.Replacee == 'Shotgun') || (e.Replacee == 'SuperShotgun') || (e.Replacee == 'Crossbow') )
{
if ( !Random[Replacements](0,3) ) e.Replacement = 'Enforcer';
else if ( Random[Replacements](0,1) ) e.Replacement = 'BioRifle';
else e.Replacement = 'ShockRifle';
}
else if ( (e.Replacee == 'Chaingun') || (e.Replacee == 'Blaster') )
{
if ( Random[Replacements](0,2) ) e.Replacement = 'PulseGun';
else e.Replacement = 'Ripper2';
}
else if ( (e.Replacee == 'RocketLauncher') || (e.Replacee == 'PhoenixRod') )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'FlakCannon';
else e.Replacement = 'UTRocketLauncher';
}
else if ( (e.Replacee == 'PlasmaRifle') || (e.Replacee == 'SkullRod') )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'Minigun';
else e.Replacement = 'SniperRifle';
}
else if ( e.Replacee == 'BFG9000' ) e.Replacement = 'WarheadLauncher';
else if ( e.Replacee == 'Mace' )
{
if ( (!deathmatch || flak_dmsshock) && !Random[Replacements](0,6) ) e.Replacement = 'EnhancedShockRifle';
else e.Replacement = 'WarheadLauncher';
}
else if ( (e.Replacee == 'Clip') || (e.Replacee == 'GoldWandAmmo') || (e.Replacee == 'GoldWandHefty') ) e.Replacement = 'EClip';
else if ( (e.Replacee == 'ClipBox') )
{
if ( !Random[Replacements](0,2) ) e.Replacement = 'EClip';
else if ( Random[Replacements](0,2) ) e.Replacement = 'PulseAmmo';
else e.Replacement = 'RipperAmmo';
}
else if ( (e.Replacee == 'BlasterAmmo') || (e.Replacee == 'BlasterHefty') )
{
if ( Random[Replacements](0,2) ) e.Replacement = 'PulseAmmo';
else e.Replacement = 'RipperAmmo';
}
else if ( (e.Replacee == 'Shell') || (e.Replacee == 'CrossbowAmmo') )
{
if ( !Random[Replacements](0,2) ) e.Replacement = 'EClip';
else if ( Random[Replacements](0,1) ) e.Replacement = 'BioAmmo2';
else e.Replacement = 'ShockAmmo2';
}
else if ( (e.Replacee == 'ShellBox') || (e.Replacee == 'CrossbowHefty') )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'BioAmmo';
else e.Replacement = 'ShockAmmo';
}
else if ( (e.Replacee == 'RocketAmmo') || (e.Replacee == 'PhoenixRodAmmo') || (e.Replacee == 'MaceAmmo') )
{
if ( Random[Replacements](0,1) )
{
if ( !Random[Replacements](0,3) ) e.Replacement = 'FlakAmmo';
else e.Replacement = 'FlakAmmo2';
}
else
{
if ( !Random[Replacements](0,3) ) e.Replacement = 'UTRocketAmmo';
else e.Replacement = 'UTRocketAmmo2';
}
}
else if ( (e.Replacee == 'RocketBox') || (e.Replacee == 'PhoenixRodHefty') || (e.Replacee == 'MaceHefty') )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'FlakAmmo';
else e.Replacement = 'UTRocketAmmo';
}
else if ( (e.Replacee == 'Cell') || (e.Replacee == 'SkullRodAmmo') )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'EClip';
else if ( !Random[Replacements](0,3) ) e.Replacement = 'RifleAmmo';
else e.Replacement = 'RifleAmmo2';
}
else if ( (e.Replacee == 'CellPack') || (e.Replacee == 'SkullRodHefty') )
{
if ( !Random[Replacements](0,6) ) e.Replacement = 'WarheadAmmo';
else if ( Random[Replacements](0,1) ) e.Replacement = 'MiniAmmo';
else e.Replacement = 'RifleAmmo';
}
else if ( e.Replacee == 'InvulnerabilitySphere' ) e.Replacement = (!deathmatch||flak_dmsshock)?'EnhancedShockRifle':'UDamage';
else if ( e.Replacee == 'Berserk' ) e.Replacement = 'UDamage';
else if ( (e.Replacee == 'ArtiTomeOfPower') || (e.Replacee == 'ArtiEgg') ) e.Replacement = 'ActUDamage';
else if ( e.Replacee == 'Soulsphere' ) e.Replacement = 'UTHealthPack';
else if ( e.Replacee == 'ArtiSuperHealth' ) e.Replacement = 'ActHealthPack';
else if ( e.Replacee == 'Megasphere' ) e.Replacement = 'UTShieldBelt';
else if ( e.Replacee == 'ArtiInvulnerability' ) e.Replacement = 'ActShieldBelt';
else if ( (e.Replacee == 'Allmap') || (e.Replacee == 'SuperMap') ) e.Replacement = 'UTMapRevealer';
else if ( e.Replacee == 'BlurSphere' ) e.Replacement = 'UTInvisibility';
else if ( e.Replacee == 'ArtiInvisibility' ) e.Replacement = 'ActInvisibility';
else if ( e.Replacee == 'Infrared' ) e.Replacement = 'Searchlight';
else if ( e.Replacee == 'ArtiTorch' ) e.Replacement = 'ActSearchlight';
else if ( e.Replacee == 'RadSuit' ) e.Replacement = 'UTJumpBoots';
else if ( e.Replacee == 'ArtiFly' ) e.Replacement = 'ActJumpBoots';
else if ( (e.Replacee == 'Backpack') || (e.Replacee == 'BagOfHolding') ) e.Replacement = 'UTBackpack';
else if ( (e.Replacee == 'ArmorBonus') || (e.Replacee == 'ArtiTimeBomb') ) e.Replacement = 'UTArmorBonus';
else if ( (e.Replacee == 'HealthBonus') || (e.Replacee == 'CrystalVial') ) e.Replacement = 'UTHealthBonus';
else if ( (e.Replacee == 'GreenArmor') || (e.Replacee == 'Silvershield') ) e.Replacement = 'UTThighPads';
else if ( (e.Replacee == 'BlueArmor') || (e.Replacee == 'EnchantedShield') ) e.Replacement = 'UTBodyArmor';
else if ( e.Replacee == 'Stimpack' ) e.Replacement = 'UTMedBox';
else if ( e.Replacee == 'Medikit' ) e.Replacement = 'UTHealthBox';
else if ( e.Replacee == 'ArtiHealth' )
{
if ( !Random[Replacements](0,3) ) e.Replacement = 'ActHealthBox';
else e.Replacement = 'ActMedBox';
}
else if ( e.Replacee == 'ArtiTeleport' )
{
// I have no idea what to replace this with, so just have some random stuff
if ( Random[Replacements](0,1) ) e.Replacement = 'UTBackpack';
else if ( Random[Replacements](0,1) ) e.Replacement = 'ActShieldBelt';
else if ( Random[Replacements](0,1) ) e.Replacement = 'ActHealthPack';
else e.Replacement = 'ActUDamage';
}
else if ( e.Replacee == 'RedCard' ) e.Replacement = 'UTRedKey';
else if ( e.Replacee == 'BlueCard' ) e.Replacement = 'UTBlueKey';
else if ( e.Replacee == 'YellowCard' ) e.Replacement = 'UTGoldKey';
else if ( e.Replacee == 'RedSkull' ) e.Replacement = 'UTRedSkull';
else if ( e.Replacee == 'BlueSkull' ) e.Replacement = 'UTBlueSkull';
else if ( e.Replacee == 'YellowSkull' ) e.Replacement = 'UTGoldSkull';
else if ( e.Replacee == 'TeleportFog' ) e.Replacement = 'UTTeleportFog';
else if ( e.Replacee == 'ItemFog' ) e.Replacement = 'UTItemFog';
else if ( flak_blood && (e.Replacee == 'Blood') ) e.Replacement = 'UTBlood';
else if ( e.Replacee == 'KeyYellow' ) e.Replacement = 'UTHereticYellowKey';
else if ( e.Replacee == 'KeyGreen' ) e.Replacement = 'UTHereticGreenKey';
else if ( e.Replacee == 'KeyBlue' ) e.Replacement = 'UTHereticBlueKey';
}
private Actor AddLight( Vector3 pos, Color col, int radius )
{
Actor l = Actor.Spawn("PointLightAttenuated",pos);
if ( !l ) return null;
l.args[0] = col.r;
l.args[1] = col.g;
l.args[2] = col.b;
l.args[3] = radius;
return l;
}
private Actor AddAmbient( Vector3 pos, String snd, double volume = 1., double attenuation = ATTN_NORM )
{
Actor a = Actor.Spawn("MapSpot",pos);
if ( !a ) return null;
a.A_PlaySound(snd,CHAN_BODY,volume,true,attenuation);
return a;
}
override void WorldLoaded( WorldEvent e )
{
// just replace the -noflat- with a better scaled version and change the sky
if ( !flak_doomtest )
{
if ( (level.GetChecksum() ~== "959A613006CC3AA912C4A22908B7566A") || (level.GetChecksum() ~== "0EADB2F82732A968B8513E4DC6138439") )
{
Level.ReplaceTextures("-noflat-","DefTex",0);
TextureID skytx = TexMan.CheckForTexture("BlueSky",TexMan.Type_Any);
level.ChangeSky(skytx,skytx);
}
return;
}
// prettify Kinsie's test map for a more Unreal feel
if ( level.GetChecksum() ~== "959A613006CC3AA912C4A22908B7566A" )
{
S_ChangeMusic("Course");
TextureID deftex = TexMan.CheckForTexture("-noflat-",TexMan.Type_Any);
TextureID skytx = TexMan.CheckForTexture("KGDaySky",TexMan.Type_Any);
TextureID baseflor = TexMan.CheckForTexture("rClfFlr0",TexMan.Type_Any);
TextureID baseceil = TexMan.CheckForTexture("rClfBas0",TexMan.Type_Any);
TextureID basewall = TexMan.CheckForTexture("uAlnWl2b",TexMan.Type_Any);
TextureID xbasewall = TexMan.CheckForTexture("xAlnWl2b",TexMan.Type_Any);
TextureID glasstex = TexMan.CheckForTexture("Glassg",TexMan.Type_Any);
level.ChangeSky(skytx,skytx);
for ( int i=0; i<level.sectors.size(); i++ )
{
level.sectors[i].lightlevel = min(level.sectors[i].lightlevel,96);
if ( level.sectors[i].GetPlaneLight(0) ) level.sectors[i].SetPlaneLight(0,96);
if ( level.sectors[i].GetPlaneLight(1) ) level.sectors[i].SetPlaneLight(1,96);
// open some ceilings
if ( level.sectors[i].ceilingplane.ZAtPoint(level.sectors[i].centerspot) == 1280 )
level.sectors[i].SetTexture(1,skyflatnum);
if ( level.sectors[i].GetTexture(0) == deftex )
{
level.sectors[i].SetTexture(0,((i==47)||((i>=256)&&(i<=260)))?baseceil:baseflor);
level.sectors[i].SetXScale(0,2.);
level.sectors[i].SetYScale(0,2.);
}
if ( level.sectors[i].GetTexture(1) == deftex )
{
level.sectors[i].SetTexture(1,((i==47)||((i>=256)&&(i<=260)))?baseflor:baseceil);
level.sectors[i].SetXScale(1,2.);
level.sectors[i].SetYScale(1,2.);
}
}
for ( int i=0; i<level.sides.size(); i++ )
{
level.sides[i].light = 0;
level.sides[i].flags &= ~Side.WALLF_ABSLIGHTING;
for ( int j=0; j<3; j++ )
{
if ( level.sides[i].GetTexture(j) != deftex ) continue;
if ( (i==529) || (i==530) || (i==533) || (i==534) )
{
level.sides[i].SetTexture(j,xbasewall);
level.sides[i].SetTextureYOffset(j,-2304);
}
else level.sides[i].SetTexture(j,basewall);
level.sides[i].SetTextureXScale(j,2.);
level.sides[i].SetTextureYScale(j,2.);
}
}
// fixup
for ( int i=215; i<246; i++ )
{
if ( (i==218) || (i==221) || (i==227)
|| (i==230) || (i==232) || (i==234)
|| (i==238) || (i==243) ) continue;
level.sectors[i].SetSpecialColor(0,"00 00 00");
}
level.sectors[53].SetFade("00 00 20");
level.sides[1844].SetTexture(1,glasstex);
level.lines[945].alpha = 0.5;
AddLight((0,-288,128),"E0 E0 FF",256);
AddLight((-2560,1024,1280),"E0 E0 FF",1024);
AddLight((0,1024,1280),"E0 E0 FF",1024);
AddLight((2560,1024,1280),"E0 E0 FF",1024);
AddLight((-384,-160,64),"FF FF FF",128);
AddLight((-384,-288,64),"FF FF FF",128);
AddLight((-384,-416,64),"FF FF FF",128);
AddLight((0,2816,96),"FF FF FF",512);
AddLight((2904,1344,128),"80 80 FF",256);
AddLight((3408,1344,128),"80 80 FF",256);
AddLight((1568,1760,64),"20 20 80",128);
AddLight((1824,1760,64),"80 20 20",128);
AddLight((2080,1760,64),"FF 80 20",128);
AddLight((2336,1760,64),"20 FF 20",128);
AddLight((2592,1760,64),"80 80 20",128);
AddLight((2848,1760,64),"A0 A0 30",128);
AddLight((2944,960,64),"20 FF 20",128);
AddLight((2944,736,64),"20 FF 20",128);
AddLight((3264,960,64),"FF 20 20",128);
AddLight((3264,736,64),"A0 A0 30",128);
AddLight((3264,512,64),"80 80 20",128);
AddLight((3584,960,64),"80 80 20",128);
AddLight((3584,736,64),"20 FF 20",128);
AddLight((3584,512,64),"FF 80 20",128);
AddLight((3584,288,64),"FF 80 FF",128);
AddAmbient((0,-288,192),"testamb/wind1",0.5,1.6);
AddAmbient((-2560,1024,768),"testamb/wind1",0.4,0.8);
AddAmbient((0,1024,768),"testamb/wind1",0.4,0.8);
AddAmbient((2560,1024,768),"testamb/wind1",0.4,0.8);
AddAmbient((768,1600,1280),"testamb/wind2",0.8,1.6);
AddAmbient((3174,1344,128),"testamb/water",0.4,1.6);
AddAmbient((1568,1760,64),"testamb/water",0.4,2.4);
AddAmbient((2080,1760,64),"testamb/lava",0.8,2.4);
AddAmbient((2336,1760,64),"testamb/slime",0.4,2.4);
AddAmbient((2944,960,64),"testamb/slime",0.4,2.4);
AddAmbient((2944,736,64),"testamb/slime",0.4,2.4);
AddAmbient((3584,736,64),"testamb/slime",0.4,2.4);
AddAmbient((3584,512,64),"testamb/lava",0.8,2.4);
}
else if ( level.GetChecksum() ~== "0EADB2F82732A968B8513E4DC6138439" )
{
S_ChangeMusic("Course");
TextureID deftex = TexMan.CheckForTexture("-noflat-",TexMan.Type_Any);
TextureID skytx = TexMan.CheckForTexture("KGDaySky",TexMan.Type_Any);
TextureID baseflor = TexMan.CheckForTexture("rClfFlr0",TexMan.Type_Any);
TextureID baseceil = TexMan.CheckForTexture("rClfBas0",TexMan.Type_Any);
TextureID basewall = TexMan.CheckForTexture("uAlnWl2b",TexMan.Type_Any);
TextureID xbasewall = TexMan.CheckForTexture("xAlnWl2b",TexMan.Type_Any);
TextureID glasstex = TexMan.CheckForTexture("Glassg",TexMan.Type_Any);
level.ChangeSky(skytx,skytx);
for ( int i=0; i<level.sectors.size(); i++ )
{
level.sectors[i].lightlevel = min(level.sectors[i].lightlevel,96);
if ( level.sectors[i].GetPlaneLight(0) ) level.sectors[i].SetPlaneLight(0,96);
if ( level.sectors[i].GetPlaneLight(1) ) level.sectors[i].SetPlaneLight(1,96);
// open some ceilings
if ( level.sectors[i].ceilingplane.ZAtPoint(level.sectors[i].centerspot) == 1280 )
level.sectors[i].SetTexture(1,skyflatnum);
if ( level.sectors[i].GetTexture(0) == deftex )
{
level.sectors[i].SetTexture(0,((i==47)||((i>=144)&&(i<=148)))?baseceil:baseflor);
level.sectors[i].SetXScale(0,2.);
level.sectors[i].SetYScale(0,2.);
}
if ( level.sectors[i].GetTexture(1) == deftex )
{
level.sectors[i].SetTexture(1,((i==47)||((i>=144)&&(i<=148)))?baseflor:baseceil);
level.sectors[i].SetXScale(1,2.);
level.sectors[i].SetYScale(1,2.);
}
}
for ( int i=0; i<level.sides.size(); i++ )
{
level.sides[i].light = 0;
level.sides[i].flags &= ~Side.WALLF_ABSLIGHTING;
for ( int j=0; j<3; j++ )
{
if ( level.sides[i].GetTexture(j) != deftex ) continue;
if ( (i==295) || (i==296) || (i==309) || (i==310) )
{
level.sides[i].SetTexture(j,xbasewall);
level.sides[i].SetTextureYOffset(j,-2304);
}
else level.sides[i].SetTexture(j,basewall);
level.sides[i].SetTextureXScale(j,2.);
level.sides[i].SetTextureYScale(j,2.);
}
}
// fixup
for ( int i=104; i<134; i++ )
{
if ( (i==107) || (i==110) || (i==116)
|| (i==119) || (i==121) || (i==127)
|| (i==132) ) continue;
level.sectors[i].SetSpecialColor(0,"00 00 00");
}
level.sectors[53].SetFade("00 00 20");
level.sides[216].SetTexture(1,glasstex);
level.lines[125].alpha = 0.5;
AddLight((0,-288,128),"E0 E0 FF",256);
AddLight((-2560,1024,1280),"E0 E0 FF",1024);
AddLight((0,1024,1280),"E0 E0 FF",1024);
AddLight((2560,1024,1280),"E0 E0 FF",1024);
AddLight((-384,-160,64),"FF FF FF",128);
AddLight((-384,-288,64),"FF FF FF",128);
AddLight((-384,-416,64),"FF FF FF",128);
AddLight((0,2816,96),"FF FF FF",512);
AddLight((2904,1344,128),"80 80 FF",256);
AddLight((3408,1344,128),"80 80 FF",256);
AddLight((1568,1760,64),"20 20 80",128);
AddLight((1824,1760,64),"A0 A0 30",128);
AddLight((2080,1760,64),"80 80 20",128);
AddLight((2336,1760,64),"20 20 80",128);
AddLight((2592,1760,64),"FF 80 20",128);
AddLight((2944,960,64),"A0 A0 30",128);
AddLight((2944,736,64),"A0 A0 30",128);
AddLight((3264,960,64),"A0 A0 30",128);
AddLight((3264,736,64),"A0 A0 30",128);
AddLight((3264,512,64),"A0 A0 30",128);
AddLight((3264,288,64),"FF 80 20",128);
AddLight((3584,960,64),"A0 A0 30",128);
AddLight((3584,736,64),"80 80 20",128);
AddLight((3584,512,64),"FF 80 20",128);
AddLight((3584,288,64),"A0 A0 30",128);
AddAmbient((0,-288,192),"testamb/wind1",0.5,1.6);
AddAmbient((-2560,1024,768),"testamb/wind1",0.4,0.8);
AddAmbient((0,1024,768),"testamb/wind1",0.4,0.8);
AddAmbient((2560,1024,768),"testamb/wind1",0.4,0.8);
AddAmbient((768,1600,1280),"testamb/wind2",0.8,1.6);
AddAmbient((3174,1344,128),"testamb/water",0.4,1.6);
AddAmbient((1568,1760,64),"testamb/water",0.4,2.4);
AddAmbient((2336,1760,64),"testamb/water",0.4,2.4);
AddAmbient((2592,1760,64),"testamb/lava",0.8,2.4);
AddAmbient((3584,512,64),"testamb/lava",0.8,2.4);
AddAmbient((3264,288,64),"testamb/lava",0.8,2.4);
}
}
override void WorldThingSpawned( WorldEvent e )
{
if ( flak_nobosstelefrag && e.Thing.bBOSS ) e.Thing.bNOTELEFRAG = true;
if ( deathmatch && flak_instagib )
{
if ( (e.Thing is 'EnhancedShockRifle') || (e.Thing is 'EnhancedShockAmmo') ) return;
if ( e.Thing is 'Inventory' ) e.Thing.Destroy();
}
}
override void PlayerEntered( PlayerEvent e )
{
if ( deathmatch && flak_instagib )
{
players[e.playernumber].mo.GiveInventory("EnhancedShockRifle",1);
return;
}
if ( flak_translocator )
players[e.playernumber].mo.GiveInventory("Translocator",1);
}
override void PlayerRespawned( PlayerEvent e )
{
if ( deathmatch && flak_instagib )
{
players[e.playernumber].mo.GiveInventory("EnhancedShockRifle",1);
return;
}
if ( flak_translocator )
players[e.playernumber].mo.GiveInventory("Translocator",1);
}
override void NetworkProcess( ConsoleEvent e )
{
if ( e.Name ~== "refreshtrans" )
{
if ( flak_translocator )
{
for ( int i=0; i<MAXPLAYERS; i++ ) if ( playeringame[i] ) players[i].mo.GiveInventory("Translocator",1);
}
else
{
for ( int i=0; i<MAXPLAYERS; i++ ) if ( playeringame[i] ) players[i].mo.TakeInventory("Translocator",1);
}
}
else if ( e.Name ~== "uttaunt" )
{
if ( (e.player == -1) || !playeringame[e.player] || !players[e.player].mo ) return;
let mo = players[e.player].mo;
if ( (mo.Health <= 0) || !(mo is 'UTPlayer') ) return;
switch ( e.Args[0] )
{
case 2:
if ( mo.FindState("Taunt2") ) mo.SetStateLabel("Taunt2");
break;
case 3:
if ( mo.FindState("Taunt3") ) mo.SetStateLabel("Taunt3");
break;
case 4:
if ( mo.FindState("Taunt4") ) mo.SetStateLabel("Taunt4");
break;
default:
if ( mo.FindState("Taunt1") ) mo.SetStateLabel("Taunt1");
break;
}
}
}
override void WorldTick()
{
for ( int i=0; i<flashes.size(); i++ )
{
if ( flashes[i].tic >= gametic ) continue;
flashes.Delete(i);
i--;
}
}
override void PostUiTick()
{
for ( int i=0; i<flashes.size(); i++ )
{
if ( flashes[i].tic < gametic ) continue;
GenericFlash gf = new("GenericFlash").Setup(flashes[i].cam,flashes[i].c,flashes[i].duration);
StatusBar.AttachMessage(gf,0,BaseStatusBar.HUDMSGLayer_UnderHUD);
}
}
override void WorldThingDamaged( WorldEvent e )
{
if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return;
if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill ) return;
if ( (e.Inflictor && e.Inflictor.player && (e.Inflictor != e.Thing)) || (e.DamageSource && e.DamageSource.player && (e.DamageSource != e.Thing)) )
{
Weapon saw = Weapon(e.Inflictor.FindInventory("UTChainsaw"));
if ( !saw ) saw = Weapon(e.DamageSource.FindInventory("UTChainsaw"));
bool current = false;
if ( e.Inflictor.player && e.Inflictor.player.ReadyWeapon == saw ) current = true;
if ( e.DamageSource.player && (e.DamageSource.player.ReadyWeapon == saw) ) current = true;
if ( flak_sawammo && saw && (saw.Ammo1.Amount < 40) && !current && !Random[SawDrop](0,9) )
{
let a = Actor.Spawn("ChainsawAmmo",e.Thing.Vec3Offset(0,0,e.Thing.Height*.5));
a.TossItem();
}
}
}
override void WorldThingDied( WorldEvent e )
{
if ( e.Thing.bDONTGIB ) return;
// gibbers
if ( flak_gibs && !e.Thing.bNOBLOOD && (e.Thing.FindState("XDeath",true) || e.Thing.FindState("Death.Extreme",true)) && ((e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.Thing.Health < e.Thing.GetGibHealth())) && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) )
{
// players use their own gibber
if ( e.Thing is 'UTPlayer' ) return;
// generic gibbing
let a = Actor.Spawn("UTGibber",e.Thing.pos);
a.vel = e.Thing.vel;
a.Scale = e.Thing.Scale;
a.Translation = e.Thing.BloodTranslation;
if ( e.Thing.BloodColor ) a.SetShade(e.Thing.BloodColor);
else a.SetShade(gameinfo.defaultbloodcolor);
a.A_SetSize(e.Thing.radius,e.Thing.height);
e.Thing.A_XScream();
e.Thing.A_NoBlocking();
e.Thing.A_BossDeath();
e.Thing.Destroy();
return;
}
// attach damage accumulator for corpses
if ( !(e.Thing is 'UTPlayer') && !flak_corpsedamage ) return;
let a = Actor.Spawn("ShredCorpseHitbox",e.Thing.pos);
a.target = e.Thing;
}
static void DoFlash( Actor camera, Color c, int duration )
{
QueuedFlash qf = new("QueuedFlash");
qf.duration = duration;
qf.c = c;
qf.tic = gametic;
qf.cam = camera;
let hnd = UTMainHandler(EventHandler.Find("UTMainHandler"));
if ( !hnd ) return; // not supposed to happen
hnd.flashes.push(qf);
}
// Doom's explosions aren't fully 3D
static void DoBlast( Actor Source, double ExplosionRadius, double MomentumTransfer )
{
BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius);
while ( bi.Next() )
{
Actor a = bi.Thing;
if ( !a || !a.bSHOOTABLE || !Source.CheckSight(a,0xf) || (a == Source) || (Source.Distance3D(a) > ExplosionRadius) )
continue;
Vector3 midpoint = a.Vec3Offset(0,0,a.height*0.5);
Vector3 dir = Level.Vec3Diff(Source.pos,midpoint);
double dist = max(1,dir.length());
double damagescale = 1-max(0,(dist-a.radius)/ExplosionRadius);
dir = dir/dist;
if ( (a is 'ShredCorpseHitbox') && a.target )
a.target.vel += dir*damagescale*(MomentumTransfer/(Thinker.TICRATE*a.target.mass));
else a.vel += dir*damagescale*(MomentumTransfer/(Thinker.TICRATE*a.mass));
}
}
// Same for this
static void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer )
{
if ( !Victim ) return;
if ( (Victim is 'ShredCorpseHitbox') && Victim.target )
Victim.target.vel += HitDirection*(MomentumTransfer/(Thinker.TICRATE*Victim.target.Mass));
else Victim.vel += HitDirection*(MomentumTransfer/(Thinker.TICRATE*Victim.Mass));
}
static void DoSwing( Actor target, Vector2 dir, double initial, double inc, int steps, int mode = 0, int delay = 0, double rmul = 1.0 )
{
if ( !flak_swingers ) return;
let s = new("Swinger");
s.ChangeStatNum(Thinker.STAT_USER);
s.target = target;
s.dir = dir;
s.inc = inc;
s.rmul = rmul;
s.steps = steps;
s.mode = mode;
s.delay = delay;
s.cnt = 0;
s.cstate = 0;
s.str = initial;
s.tstr = initial;
}
}