flak_m/zscript/utcommon.zsc
2020-10-25 09:56:05 +01:00

3705 lines
99 KiB
Text

// constants for extra sound stuff, all starting at 0x4360 (436 was the last official UT version)
const CHAN_ANNOUNCER = 0x4360; // announcer voices
const CHAN_FOOTSTEP = 0x4361; // footsteps and splashes (should have OVERLAP flag)
const CHAN_LEFTWEAPON = 0x4362; // for dual wielded weapons
const CHAN_WEAPONMISC = 0x4363; // extra sounds (e.g.: idle loops)
const CHAN_LEFTWEAPONMISC = 0x4364; // ... for dual wielded weapons
const CHAN_POWERUP = 0x4365; // powerup/item use sounds
const CHAN_POWERUP2 = 0x4366; // auxiliary powerup sounds
const CHAN_POWERUP3 = 0x4367; // even more powerup sounds (used by jump boots, mainly)
const CHAN_POWERUP4 = 0x4368; // even more (used by shield belt / invuln hit sounds)
Class UTPlayer : DoomPlayer
{
bool doprintnoammo;
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;
Actor underwatersnd;
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
// in non-vanilla mode, also gives body/thigh armor
Class<Inventory> which[] =
{
"UTShieldBelt", "UTArmorBonus",
"UTBodyArmor", "UTThighpads"
};
Inventory inv;
int mx = flak_vanillaarmor?2:4;
for ( int i=0; i<mx; i++ )
{
inv = Inventory(Spawn(which[i]));
inv.ClearCounters();
inv.Amount = inv.MaxAmount;
if ( !inv.CallTryPickup(self) ) inv.Destroy();
}
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")))
|| ((gameinfo.gametype&GAME_HERETIC) && (type is 'ActUTFullAmmoBox')) )
{
// don't give activatables outside of Heretic/Hexen
// don't give full ammo cubes outside of Hexen
continue;
}
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);
}
// modified so it displays the "has no ammo" message
override Weapon PickWeapon( int slot, bool checkammo )
{
doprintnoammo = true;
let rslt = Super.PickWeapon(slot,checkammo);
doprintnoammo = false;
return rslt;
}
override void Tick()
{
Super.Tick();
if ( !flak_utmovement || !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
{
bNOFRICTION = false;
bNOFRICTIONBOUNCE = false;
}
else
{
bNOFRICTION = true;
bNOFRICTIONBOUNCE = true;
}
if ( InStateSequence(CurState,FindState("See",true)) )
SetStateLabel("See2");
if ( (waterlevel >= 2) && (lastwaterlevel < 2) )
PlaySplash(1.);
else if ( (waterlevel < 2) && (lastwaterlevel >= 2) )
PlaySurface();
if ( (waterlevel >= 3) && !underwatersnd )
{
underwatersnd = Spawn("UTUnderSound",pos);
underwatersnd.target = self;
}
else if ( (waterlevel < 3) && underwatersnd )
underwatersnd.Destroy();
lastwaterlevel = waterlevel;
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 ) PlaySplash(vol);
else A_StartSound("*uland",CHAN_FOOTSTEP,CHANF_OVERLAP,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 ) PlayWetFootstep(vol);
else PlayFootstep(vol);
}
lastground = player.onground;
lastvelz = prevvelz;
prevvelz = vel.z;
}
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) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
{
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
Vector3 floornormal = floorsector.floorplane.normal;
for ( int i=0; i<floorsector.Get3DFloorCount(); i++ )
{
F3DFloor ff = floorsector.Get3DFloor(i);
if ( ff.top.ZAtPoint(pos.xy) ~== floorz )
{
floornormal = -ff.top.normal;
break;
}
}
if ( floornormal 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 < 3) )
{
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_StartSound("*jump",CHAN_VOICE);
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 < 1) )
{
// 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;
// in UT you can't crouch unless you're on the ground
// however for the sake of compatibility with a whole lot of maps, crouch-jumping will be a thing here
if ( CanCrouch() && (player.health > 0) && level.IsCrouchingAllowed() )
{
if ( !totallyfrozen )
{
int crouchdir = player.crouching;
if ( !crouchdir ) crouchdir = (player.cmd.buttons&BT_CROUCH)?-1:1;
else if ( player.cmd.buttons&BT_CROUCH ) player.crouching = 0;
if ( (crouchdir == 1) && (player.crouchfactor < 1) && (pos.Z+height < ceilingz) )
CrouchMove(1);
else if ( (crouchdir == -1) && (player.crouchfactor > 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_StartSound("*jump",CHAN_VOICE);
}
last_jump_held = gametic;
}
if ( !player.onground || player.jumptics )
last_jump_held = gametic;
}
override void DeathThink()
{
Super.DeathThink();
if ( !flak_utmovement || !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
return;
// unreal physics while dead
double friction = FrictionToUnreal();
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// 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 < 1) )
{
// 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_StartSound("ut/playerfootstep",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
}
virtual void PlaySplash( double vol )
{
TerrainDef t = GetFloorTerrain();
String snd = "ut/wetsplash";
switch ( t.TerrainName )
{
case 'UTLava':
case 'Lava':
snd = "ut/lavasplash";
break;
case 'UTSlime':
case 'UTNukage':
case 'Slime':
case 'Nukage':
case 'Sludge':
snd = "ut/slimesplash";
break;
}
A_StartSound(snd,CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
}
virtual void PlaySurface()
{
TerrainDef t = GetFloorTerrain();
String snd = "ut/wetsurface";
switch ( t.TerrainName )
{
case 'UTLava':
case 'Lava':
snd = "ut/lavasurface";
break;
case 'UTSlime':
case 'UTNukage':
case 'Slime':
case 'Nukage':
case 'Sludge':
snd = "ut/slimesurface";
break;
}
A_StartSound(snd,CHAN_FOOTSTEP,CHANF_OVERLAP);
}
virtual void PlayWetFootstep( double vol )
{
TerrainDef t = GetFloorTerrain();
String snd = "ut/playerfootstepwet";
switch ( t.TerrainName )
{
case 'UTLava':
case 'Lava':
snd = "ut/playerfootsteplava";
break;
case 'UTSlime':
case 'UTNukage':
case 'Slime':
case 'Nukage':
case 'Sludge':
snd = "ut/playerfootstepslime";
break;
}
A_StartSound(snd,CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
}
override void PlayIdle()
{
if ( player.Health <= 0 ) return;
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// 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 ( player.crouchdir == -1 )
{
// Crouching
if ( !InStateSequence(CurState,FindState("SpawnCrouch")) )
SetStateLabel("SpawnCrouch");
}
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 < 1) )
{
// 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.Health <= 0 ) return;
if ( !bNoGravity && player.onground && (waterlevel < 3) )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( !InStateSequence(CurState,FindState("SeeCrouch")) )
SetStateLabel("SeeCrouch");
}
else 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 < 1) && abs(pos.z-floorz) > maxstepheight )
{
// 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()
{
if ( player.Health <= 0 ) return;
// 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.Health <= 0 ) return;
if ( (player && (player.mo == self)) && (player.crouchdir == -1) ) return;
SetStateLabel("Missile");
}
virtual void PlayReloading()
{
if ( player.Health <= 0 ) return;
if ( (player && (player.mo == self)) && (player.crouchdir == -1) ) return;
SetStateLabel("Reload");
}
virtual void PlayLanding()
{
if ( player.Health <= 0 ) return;
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();
}
// mostly a copy of A_FreezeDeathChunks but with some DT specific gib
// behaviour
void A_FreezeDeathChunksDT()
{
if ( (vel != (0,0,0)) && !bShattering )
{
tics = 3*TICRATE;
return;
}
vel = (0,0,0);
A_StartSound("misc/icebreak");
// [RH] In Hexen, this creates a random number of shards (range [24,56])
// with no relation to the size of the self shattering. I think it should
// base the number of shards on the size of the dead thing, so bigger
// things break up into more shards than smaller things.
// An actor with radius 20 and height 64 creates ~40 chunks.
int numChunks = max(4,int(radius*Height)/32);
int i = Random[FreezeDeathChunks](0,numChunks/4-1);
for ( i=max(24,numChunks+i); i>=0; i-- )
{
double xo = (random[FreezeDeathChunks]()-128)*radius/128;
double yo = (random[FreezeDeathChunks]()-128)*radius/128;
double zo = (random[FreezeDeathChunks]()*Height/255);
Actor mo = Spawn("IceChunk",Vec3Offset(xo,yo,zo),ALLOW_REPLACE);
if ( !mo ) continue;
mo.SetState(mo.SpawnState+random[FreezeDeathChunks](0,2));
mo.Vel.X = random2[FreezeDeathChunks]()/128.;
mo.Vel.Y = random2[FreezeDeathChunks]()/128.;
mo.Vel.Z = (mo.pos.Z-pos.Z)/Height*4;
}
// Gib the player, the camera will attach to the head at this point
A_GibMe();
A_NoBlocking();
SetStateLabel("IceBurst");
}
void A_PainDT()
{
if ( waterlevel > 2 ) A_StartSound("*death-drowning",CHAN_VOICE);
else A_Pain();
}
void A_PlayerScreamDT()
{
if ( waterlevel > 2 ) A_StartSound("*death-drowning",CHAN_VOICE);
else A_PlayerScream();
}
void A_XScreamDT()
{
if ( waterlevel > 2 ) A_StartSound("*death-drowning",CHAN_VOICE);
else A_XScream();
}
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_PainDT();
Goto Spawn;
#### # 2;
PLAY Y 3 A_PainDT();
Goto Spawn;
#### # 2;
PLAY Z 3 A_PainDT();
Goto Spawn;
Pain.Decapitated:
#### # 2;
PLAY X 3 A_PainDT();
Goto Spawn;
Death.Decapitated:
#### # 0 A_HeadPop();
PLD4 A 3 A_PlayerScreamDT();
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_PlayerScreamDT();
PD11 B 3 A_NoBlocking();
PD11 CDEFGHIJKLMNOPQ 3;
PD11 R 1 A_DMFade();
Wait;
Death.Zapped:
#### # 3;
PLD9 A 3 A_PlayerScreamDT();
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_PlayerScreamDT();
PLD8 B 3 A_NoBlocking();
PLD8 CDEFGHIJKLMNOPQ 3;
PLD8 R 1 A_DMFade();
Wait;
Death7:
#### # 3;
PLD7 A 3 A_PlayerScreamDT();
PLD7 B 3 A_NoBlocking();
PLD7 CDEFGHIJKLMNOPQRST 3;
PLD7 U 1 A_DMFade();
Wait;
Death3:
#### # 3;
PLD3 A 3 A_PlayerScreamDT();
PLD3 B 3 A_NoBlocking();
PLD3 CDEFGHIJKL 3;
PLD3 M 1 A_DMFade();
Wait;
Death2:
#### # 3;
PLD2 A 3 A_PlayerScreamDT();
PLD2 B 3 A_NoBlocking();
PLD2 CDEFGHIJKLMNO 3;
PLD2 P 1 A_DMFade();
Wait;
Death1:
#### # 3;
PLD1 A 3 A_PlayerScreamDT();
PLD1 B 3 A_NoBlocking();
PLD1 CDEFGHIJKL 3;
PLD1 M 1 A_DMFade();
Wait;
XDeath:
TNT1 A 350
{
A_XScreamDT();
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;
Ice:
PICE A 5 A_FreezeDeath();
PICE A 1 A_FreezeDeathChunksDT();
Wait;
IceBurst:
TNT1 A 35;
TNT1 A 1 A_CheckPlayerDone();
Wait;
}
}
Class UTUnderSound : Actor
{
enum EFluidType
{
FLUID_WATER,
FLUID_SLIME,
FLUID_LAVA,
FLUID_NITRO
};
int curfluid, lastfluid;
String fluidsounds[4];
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+NOINTERACTION;
}
int GetFluid()
{
int thisfluid = FLUID_WATER;
// try to guess where we are
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
F3DFloor ff = CurSector.Get3DFloor(i);
switch ( ff.model.damagetype )
{
case 'Slime':
thisfluid = FLUID_SLIME;
break;
case 'Fire':
thisfluid = FLUID_LAVA;
break;
case 'Ice':
thisfluid = FLUID_NITRO;
break;
}
}
return thisfluid;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetOrigin(target.pos,true);
curfluid = lastfluid = GetFluid();
fluidsounds[0] = 'ut/underwater';
fluidsounds[1] = 'ut/underslime';
fluidsounds[2] = 'ut/underlava';
fluidsounds[3] = 'ut/undernitro';
A_StartSound(fluidsounds[curfluid],CHAN_VOICE,CHANF_LOOPING|CHANF_LISTENERZ,1.,0.);
A_SoundVolume(CHAN_VOICE,(players[consoleplayer].camera==target)?1.:0.);
}
override void OnDestroy()
{
A_StopSound(CHAN_VOICE);
Super.OnDestroy();
}
override void Tick()
{
Super.Tick();
if ( !target || (target.waterlevel < 3) )
{
Destroy();
return;
}
SetOrigin(target.pos,true);
curfluid = GetFluid();
if ( curfluid != lastfluid )
A_StartSound(fluidsounds[curfluid],CHAN_VOICE,CHANF_LOOPING|CHANF_LISTENERZ,1.,0.);
lastfluid = curfluid;
A_SoundVolume(CHAN_VOICE,(players[consoleplayer].camera==target)?1.:0.);
}
}
// terrain splashes
Class UTTerrainFX : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
}
States
{
Spawn:
SPSH ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 A_FadeOut(1./26.);
Stop;
}
}
Class UTWaterSplish : UTTerrainFX
{
override void PostBeginPlay()
{
Super.PostBeginPlay();
let r = Spawn("WaterRing",pos);
r.scale *= 0.3;
}
}
Class UTWaterSplash : UTTerrainFX
{
override void PostBeginPlay()
{
Super.PostBeginPlay();
Spawn("WaterRing",pos);
}
}
Class WaterRing : Actor
{
Default
{
RenderStyle "Add";
Scale 0.15;
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
alpha -= 1./20.;
}
States
{
Spawn:
RNGX ABCDEF 5 Bright;
Stop;
}
}
Class UTBloodSplish : UTTerrainFX
{
}
Class UTBloodSplash : UTTerrainFX
{
}
Class UTSlimeSplish : UTTerrainFX
{
}
Class UTSlimeSplash : UTTerrainFX
{
}
Class UTNukageSplish : UTTerrainFX
{
Default
{
+BRIGHT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
int numpt = Random[Terrain](3,5);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Terrain](-1,1),FRandom[Terrain](-1,1),FRandom[Terrain](-1,1)).unit()*FRandom[Terrain](2,5);
let s = Spawn("BioSpark",pos);
s.vel = pvel;
}
numpt = Random[Terrain](1,2);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Terrain](-1,1),FRandom[Terrain](-1,1),FRandom[Terrain](-1,1)).unit()*FRandom[Terrain](.6,1.2);
let s = Spawn("UTSmoke",pos);
s.vel = pvel;
s.scale *= 0.7;
s.A_SetRenderStyle(0.5,STYLE_AddShaded);
if ( Random[Terrain](0,1) ) s.SetShade("40FF60");
else s.SetShade("60FF40");
}
}
}
Class UTNukageSplash : UTTerrainFX
{
Default
{
+BRIGHT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
int numpt = Random[Terrain](8,12);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Terrain](-1,1),FRandom[Terrain](-1,1),FRandom[Terrain](-1,1)).unit()*FRandom[Terrain](3,12);
let s = Spawn("BioSpark",pos);
s.vel = pvel;
}
numpt = Random[Terrain](3,5);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Terrain](-1,1),FRandom[Terrain](-1,1),FRandom[Terrain](-1,1)).unit()*FRandom[Terrain](1.2,2.4);
let s = Spawn("UTSmoke",pos);
s.vel = pvel;
s.scale *= 2;
s.A_SetRenderStyle(0.5,STYLE_AddShaded);
if ( Random[Terrain](0,1) ) s.SetShade("40FF60");
else s.SetShade("60FF40");
}
}
}
Class UTLavaSplish : UTTerrainFX
{
Default
{
+BRIGHT;
}
}
Class UTLavaSplash : UTTerrainFX
{
Default
{
+BRIGHT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
Spawn("FlameExplosion",pos);
}
}
Class FlameExplosion : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
Scale 0.8;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("ut/lavaex",CHAN_VOICE);
Spawn("SlugSmoke",pos);
Spawn("SlugLight",pos);
}
States
{
Spawn:
FEXP ABCDEFGHIJ 2 BRIGHT;
Stop;
}
}
Class UTNitroSplish : UTTerrainFX
{
}
Class UTNitroSplash : UTTerrainFX
{
}
// 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;
}
States
{
See:
TMAL A -1;
Stop;
}
}
Class UTPlayerTMale2 : UTPlayer
{
Default
{
Player.SoundClass "tmale2";
Player.DisplayName "$N_TMALE2";
Player.Portrait "Brock";
UTPlayer.VoiceType VOICE_MaleTwo;
-NOMENU;
}
States
{
See:
TMAL B -1;
Stop;
}
}
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_PlayerScreamDT();
PLD6 B 3 A_NoBlocking();
PLD6 CDEFGHIJ 3;
PLD6 K 1 A_DMFade();
Wait;
Death.Zapped:
#### # 3;
PLD9 A 3 A_PlayerScreamDT();
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_PlayerScreamDT();
PLD7 B 3 A_NoBlocking();
PLD7 CDEFGHIJ 3;
PLD7 K 1 A_DMFade();
Wait;
Death5:
#### # 0 A_LegPop();
PLD5 A 3 A_PlayerScreamDT();
PLD5 B 3 A_NoBlocking();
PLD5 CDEFGHIJKL 3;
PLD5 M 1 A_DMFade();
Wait;
Death4:
#### # 3;
PLD4 A 3 A_PlayerScreamDT();
PLD4 B 3 A_NoBlocking();
PLD4 CDEFGHIJKL 3;
PLD4 M 1 A_DMFade();
Wait;
Death3:
#### # 3;
PLD3 A 3 A_PlayerScreamDT();
PLD3 B 3 A_NoBlocking();
PLD3 CDEFGHIJKLMNO 3;
PLD3 P 1 A_DMFade();
Wait;
Death2:
#### # 3;
PLD2 A 3 A_PlayerScreamDT();
PLD2 B 3 A_NoBlocking();
PLD2 CDEFGHIJKLMNOPQ 3;
PLD2 R 1 A_DMFade();
Wait;
Death1:
#### # 3;
PLD1 A 3 A_PlayerScreamDT();
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;
}
States
{
See:
TFEM A -1;
Stop;
}
}
Class UTPlayerTFemale2 : UTPlayerTFemale
{
Default
{
Player.SoundClass "tfemale";
Player.DisplayName "$N_TFEMALE2";
Player.Portrait "Lauren";
UTPlayer.VoiceType VOICE_FemaleTwo;
-NOMENU;
}
States
{
See:
TFEM B -1;
Stop;
}
}
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_StartSound("ut/bossfootstep",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
else Super.PlayFootstep(vol);
}
States
{
See:
TBOS A -1;
Stop;
}
}
Class UTWeapon : Weapon
{
int DropAmmo;
bool bExtraPickup;
transient int lastnoammotic;
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 DetachFromOwner()
{
Owner.A_StopSound(CHAN_WEAPON);
Owner.A_StopSound(CHAN_WEAPONMISC);
Owner.A_StopSound(CHAN_LEFTWEAPON);
Owner.A_StopSound(CHAN_LEFTWEAPONMISC);
Super.DetachFromOwner();
}
override void OwnerDied()
{
if ( Owner.player && (Owner.player.ReadyWeapon == self) )
{
Owner.A_StopSound(CHAN_WEAPON);
Owner.A_StopSound(CHAN_WEAPONMISC);
Owner.A_StopSound(CHAN_LEFTWEAPON);
Owner.A_StopSound(CHAN_LEFTWEAPONMISC);
}
A_ClearRefire();
Super.OwnerDied();
}
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;
}
override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount )
{
bool rslt = Super.CheckAmmo(fireMode,autoSwitch,requireAmmo,ammocount);
if ( (gametic <= lastnoammotic) || rslt || !Owner.CheckLocalView() || !(Owner is 'UTPlayer') || !UTPlayer(Owner).doprintnoammo ) return rslt;
lastnoammotic = gametic;
Console.Printf(StringTable.Localize((Amount>1)?"$M_NOAMMO2":"$M_NOAMMO"),GetTag());
return rslt;
}
// For clips
virtual clearscope int, int, bool, bool GetClipAmount() const
{
return -1, -1, false, false;
}
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*(.5+.5*cos(GetAge()*30)));
args[LIGHT_GREEN] = int(160*alpha*(.5+.5*cos(GetAge()*30)));
args[LIGHT_BLUE] = int(255*alpha*(.5+.5*cos(GetAge()*30)));
args[LIGHT_INTENSITY] = Random[Tele](10,14)*8;
alpha -= 1./TICRATE;
}
}
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 -= 1.5/TICRATE;
}
}
Class UTTeleportFog : Actor
{
Default
{
+NOBLOCKMAP;
+NOTELEPORT;
+NOGRAVITY;
RenderStyle "Add";
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
Spawn("UTTeleportLight",Vec3Offset(0,0,16));
A_StartSound("misc/teleport");
Spawn("UTTeleportParticles",Vec3Offset(0,0,16));
}
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",Vec3Offset(0,0,16));
Spawn("UTRespawnParticles",Vec3Offset(0,0,12));
}
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 = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*ofs.x+y*ofs.y+z*ofs.z);
SetOrigin(origin,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 = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*ofs.x+y*ofs.y+z*ofs.z);
SetOrigin(origin,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);
if ( target.bIceCorpse ) bNOBLOOD = true; // ice statue
}
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_StartSound("misc/corpsefall",CHAN_BODY,CHANF_DEFAULT,clamp(-lastvel.z*.2,.1,1.));
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 chunks if frozen
if ( target.bIceCorpse )
{
target.bSHATTERING = true;
if ( target is 'UTPlayer' ) UTPlayer(target).A_FreezeDeathChunksDT();
else target.A_FreezeDeathChunks();
Destroy();
return 0;
}
// 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/LT_TexturePaletteLoop
Class PaletteLight : DynamicLight
{
Color pal[256];
bool IsLooping;
Default
{
Tag "Explosion";
DynamicLight.Type "Point";
Args 0,0,0,80;
ReactionTime 15;
}
private void UpdateLight()
{
int index = 255-((255*ReactionTime)/abs(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);
}
if ( ReactionTime < 0 )
{
ReactionTime = -ReactionTime;
IsLooping = true;
}
UpdateLight();
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
ReactionTime--;
if ( ReactionTime < 0 )
{
if ( !IsLooping )
{
Destroy();
return;
}
else ReactionTime = abs(default.ReactionTime);
}
UpdateLight();
}
}
// A bParticles type actor, reads from the anivfile directly
// Tag: semicolon-separated, starting with the model, then the particle class
// of UTMeshParticle to use
Class UTParticleMesh : Actor
{
Array<double> px, py, pz;
Array<Actor> parts;
Class<UTMeshParticle> pclass;
int numframes, numverts;
double animframe, animrate;
Default
{
Tag "telepo;UTMeshParticle";
Args 28; // animation rate in fps
ReactionTime 35; // total lifespan
XScale 0.015; // scale of the model
YScale 0.03; // scale of the model
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
Array<String> strs;
strs.Clear();
GetTag().Split(strs,";");
pclass = strs[1];
int lump = Wads.CheckNumForFullname(String.Format("models/%s_a.3d",strs[0]));
String anivfile = Wads.ReadLump(lump);
numframes = anivfile.ByteAt(0);
numframes |= anivfile.ByteAt(1)<<8;
int fsiz = anivfile.ByteAt(2);
fsiz |= anivfile.ByteAt(3)<<8;
numverts = fsiz/4;
px.Resize(numverts*numframes);
py.Resize(numverts*numframes);
pz.Resize(numverts*numframes);
parts.Resize(numverts);
int cursor = 4;
for ( int i=0; i<numverts*numframes; i++ )
{
int avert = anivfile.ByteAt(cursor++);
avert |= anivfile.ByteAt(cursor++)<<8;
avert |= anivfile.ByteAt(cursor++)<<16;
avert |= anivfile.ByteAt(cursor++)<<24;
int ax = ((avert&0x7ff)<<21),
ay = ((avert>>11)&0x7ff)<<21,
az = ((avert>>22)&0x3ff)<<22;
px[i] = ax/2097152.;
py[i] = ay/2097152.;
pz[i] = az/4194304.;
}
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
for ( int i=0; i<numverts; i++ )
parts[i] = Spawn(pclass,level.Vec3Offset(pos,px[i]*scale.x*x+py[i]*scale.x*y+pz[i]*scale.y*z));
animframe = 0;
animrate = Args[0];
}
override void OnDestroy()
{
for ( int i=0; i<numverts; i++ )
{
if ( !parts[i] ) continue;
parts[i].Destroy();
}
Super.OnDestroy();
}
override void Tick()
{
Super.Tick();
animframe += animrate/TICRATE;
ReactionTime--;
if ( (ceil(animframe) >= numframes) || (ReactionTime <= 0) )
{
Destroy();
return;
}
int framea = int(floor(animframe)), frameb = int(ceil(animframe));
double theta = animframe-framea;
Vector3 posa, posb, ipos;
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
for ( int i=0; i<numverts; i++ )
{
posa = (px[i+numverts*framea],py[i+numverts*framea],pz[i+numverts*framea]);
posb = (px[i+numverts*frameb],py[i+numverts*frameb],pz[i+numverts*frameb]);
ipos = posa*(1.-theta)+posb*theta;
if ( !parts[i] ) parts[i] = Spawn(pclass,level.Vec3Offset(pos,ipos.x*scale.x*x+ipos.y*scale.x*y+ipos.z*scale.y*z));
else parts[i].SetOrigin(level.Vec3Offset(pos,ipos.x*scale.x*x+ipos.y*scale.x*y+ipos.z*scale.y*z),true);
parts[i].alpha = ReactionTime/double(default.ReactionTime);
}
}
States
{
Spawn:
TNT1 A -1;
Stop;
}
}
// Individual particle
Class UTMeshParticle : Actor
{
Default
{
RenderStyle "Add";
Scale 0.25;
FloatBobPhase 0;
+NOGRAVITY;
+NOCLIP;
+NOTELEPORT;
+DONTSPLASH;
+FORCEXYBILLBOARD;
}
States
{
Spawn:
DBEF A -1 Bright;
Stop;
}
}
Class UTRespawnParticles : UTParticleMesh
{
Default
{
Tag "telepo;UTRespawnParticle";
}
}
Class UTRespawnParticle : UTMeshParticle
{
Default
{
Scale 1.1;
}
override void Tick()
{
Super.Tick();
A_SetScale(0.03+alpha*0.37);
}
}
Class UTTeleportParticles : UTParticleMesh
{
Default
{
Tag "telepo;UTTeleParticle";
Args 21;
XScale 0.06;
YScale 0.16;
}
}
Class UTTeleParticle : UTMeshParticle
{
Default
{
Scale 0.2;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetState(FindState("Spawn")+Random[UTParticle](0,7));
}
States
{
Spawn:
UTFL ABCDEFGH -1 Bright;
Stop;
}
}
Class WaterHit
{
Sector sect;
Vector3 hitpos;
}
Class UTInvisibleSplasher : Actor
{
States
{
Spawn:
TNT1 A 2;
Stop;
}
}
// Follows a single line trail, spawns bubbles if underwater
// In addition, also activates shoot-through lines, since LineTrace can't do it
// Also spawns splashes when crossing water sectors
Class UTBulletTrail : LineTracer
{
Array<WaterHit> WaterHitList;
Array<Line> ShootThroughList;
Actor ignoreme;
static play void DoTrail( Actor target, Vector3 pos, Vector3 dir, int dist, int bubblechance )
{
let t = new("UTBulletTrail");
t.ignoreme = target;
t.WaterHitList.Clear();
t.ShootThroughList.Clear();
t.Trace(pos,level.PointInSector(pos.xy),dir,dist,0);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Actor.Spawn("UTInvisibleSplasher",t.WaterHitList[0].hitpos);
b.A_CheckTerrain();
}
for ( int i=5; i<t.Results.Distance; i+=10 )
{
if ( !Random[Boolet](0,bubblechance) ) continue;
let b = Actor.Spawn("UTBubble",level.Vec3Offset(pos,dir*i));
b.Scale *= FRandom[Boolet](0.4,0.6);
}
t.Destroy();
}
override ETraceStatus TraceCallback()
{
// liquid splashes
if ( Results.CrossedWater )
{
let hl = new("WaterHit");
hl.sect = Results.CrossedWater;
hl.hitpos = Results.CrossedWaterPos;
WaterHitList.Push(hl);
}
else if ( Results.Crossed3DWater )
{
let hl = new("WaterHit");
hl.sect = Results.Crossed3DWater;
hl.hitpos = Results.Crossed3DWaterPos;
WaterHitList.Push(hl);
}
if ( Results.HitType == TRACE_HitActor )
{
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
return TRACE_Skip;
}
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
{
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
return TRACE_Stop;
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
return TRACE_Stop;
}
}
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[3];
ui void StartMenu()
{
tex[1] = TexMan.CheckForTexture("graphics/DTLogo.png",TexMan.Type_Any);
CVar protomenu = CVar.GetCVar('flak_protomenu',players[consoleplayer]);
if ( !protomenu ) return; // this can happen
int proto = protomenu.GetInt();
if ( proto )
{
tex[0] = TexMan.CheckForTexture("graphics/UTProtoBg.png",TexMan.Type_Any);
tex[2] = TexMan.CheckForTexture("graphics/protobg.png",TexMan.Type_Any);
if ( gamestate != GS_TITLELEVEL ) return;
if ( proto > 1 ) S_ChangeMusic("menu2");
else S_ChangeMusic("xyzdMenu");
}
else
{
tex[0] = TexMan.CheckForTexture("graphics/UTBg.png",TexMan.Type_Any);
tex[2] = TexMan.CheckForTexture("graphics/finalbg.png",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 RenderUnderlay( RenderEvent e )
{
if ( gamestate != GS_TITLELEVEL ) return;
double ar = Screen.GetAspectRatio();
Vector2 tsize = TexMan.GetScaledSize(tex[0]);
double sar = tsize.x/tsize.y;
Vector2 vsize;
if ( sar > ar ) vsize = (tsize.y*ar,tsize.y);
else if ( sar < ar ) vsize = (tsize.x,tsize.x/ar);
else vsize = tsize;
Screen.DrawTexture(tex[0],false,(vsize.x-tsize.x)/2,(vsize.y-tsize.y)/2,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true);
tsize = TexMan.GetScaledSize(tex[1]);
sar = tsize.x/tsize.y;
if ( sar > ar ) vsize = (tsize.x,tsize.x/ar);
else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y);
else vsize = tsize;
Screen.DrawTexture(tex[1],false,(vsize.x-tsize.x)/2,(vsize.y-tsize.y)/2,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true);
}
override void RenderOverlay( RenderEvent e )
{
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 || (Menu.GetCurrentMenu() is 'ConversationMenu') ) return;
if ( !CVar.GetCVar('flak_showmenu',players[consoleplayer]).GetBool() ) return;
Screen.Dim("Black",1.,0,0,Screen.GetWidth(),Screen.GetHeight());
Screen.DrawTexture(tex[2],true,0,0,DTA_VirtualWidth,1024,DTA_VirtualHeight,768);
}
}
// nothing at all
Class UTNothing : Actor
{
States
{
Spawn:
TNT1 A 1;
Stop;
}
}
Class UTMainHandler : EventHandler
{
Array<QueuedFlash> flashes;
TextureID gframes[166];
transient ui int gframe;
transient ui Font rfont;
bool isbd, ispb;
override void CheckReplacement( ReplaceEvent e )
{
if ( e.IsFinal ) return;
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.Replacee == 'Mace') ) 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 = 'UTInvulnerability';
else if ( e.Replacee == 'Berserk' ) e.Replacement = 'UDamage';
else if ( (e.Replacee == 'ArtiTomeOfPower') || (e.Replacee == 'ArtiEgg') || (e.Replacee == 'ArtiPork') ) 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.Replacee == 'EnchantedShield') || (e.Replacee == 'ArtiBoostArmor') ) e.Replacement = 'UTShieldBelt';
else if ( (e.Replacee == 'ArtiInvulnerability') || (e.Replacee == 'ArtiInvulnerability2') ) e.Replacement = 'ActUTInvulnerability';
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 = 'ActUTInvisibility';
else if ( e.Replacee == 'Infrared' ) e.Replacement = 'UTNightVision';
else if ( e.Replacee == 'ArtiTorch' ) e.Replacement = 'ActUTNightVision';
else if ( e.Replacee == 'RadSuit' ) e.Replacement = 'UTJumpBoots';
else if ( (e.Replacee == 'ArtiFly') || (e.Replacee == 'ArtiSpeedBoots') ) e.Replacement = 'ActJumpBoots';
else if ( (e.Replacee == 'Backpack') || (e.Replacee == 'BagOfHolding') || (e.Replacee == 'ArtiHealingRadius') ) e.Replacement = 'UTBackpack';
else if ( (e.Replacee == 'ArmorBonus') || (e.Replacee == 'ArtiTimeBomb') || (e.Replacee == 'ArtiBlastRadius') ) e.Replacement = 'UTArmorBonus';
else if ( (e.Replacee == 'HealthBonus') || (e.Replacee == 'CrystalVial') ) e.Replacement = 'UTHealthBonus';
else if ( (e.Replacee == 'GreenArmor') || (e.Replacee == 'AmuletOfWarding') || (e.Replacee == 'PlatinumHelm') ) e.Replacement = 'UTThighPads';
else if ( e.Replacee == 'Silvershield' )
{
if ( Random[Replacements](0,1) ) e.Replacement = 'UTThighPads';
else e.Replacement = 'UTBodyArmor';
}
else if ( (e.Replacee == 'BlueArmor') || (e.Replacee == 'FalconShield') || (e.Replacee == 'MeshArmor') ) 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') || (e.Replacee == 'ArtiTeleportOther') || (e.Replacee == 'ArtiDarkServant') )
{
// I have no idea what to replace this with, so just have some random stuff
if ( (gameinfo.gametype&GAME_Hexen) && Random[Replacements](0,1) ) e.Replacement = 'UTBackpack';
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.Replacee == 'BloodSplatter') || (e.Replacee == 'AxeBlood')) ) 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';
else if ( e.Replacee.GetClassName() == 'KeyRed' )
{
// pretty sure that is a standardized name, so it should work for any heretic mod that adds it
e.Replacement = 'UTHereticRedKey';
}
// Hexen weapon replacements will attempt to follow some sort of progression
// (also additional support for Hexmas because of course)
else if ( (e.Replacee == 'FWeapFist') || (e.Replacee == 'CWeapMace') || (e.Replacee == 'MWeapWand') )
e.Replacement = 'ImpactHammer';
else if ( e.Replacee == 'FWeapAxe' ) e.Replacement = 'BioRifle';
else if ( e.Replacee == 'CWeapStaff' ) e.Replacement = 'ShockRifle';
else if ( e.Replacee == 'MWeapFrost' ) e.Replacement = 'PulseGun';
else if ( (e.Replacee == 'FWeaponPiece3') || (e.Replacee == 'FWeapQuietus') || (e.Replacee.GetClassName() == 'mkFullQuietus') ) e.Replacement = 'FlakCannon';
else if ( e.Replacee == 'CWeaponPiece3' ) e.Replacement = 'UTBackpack';
else if ( e.Replacee == 'MWeaponPiece3' ) e.Replacement = 'Enforcer';
else if ( e.Replacee == 'FWeapHammer' ) e.Replacement = 'Ripper2';
else if ( e.Replacee == 'CWeapFlame' ) e.Replacement = 'Minigun';
else if ( e.Replacee == 'MWeapLightning' ) e.Replacement = 'SniperRifle';
else if ( e.Replacee == 'FWeaponPiece2' ) e.Replacement = 'UTChainsaw';
else if ( (e.Replacee == 'CWeaponPiece2') || (e.Replacee == 'CWeapWraithverge') || (e.Replacee.GetClassName() == 'mkFullWraithverge') ) e.Replacement = 'UTRocketLauncher';
else if ( e.Replacee == 'MWeaponPiece2' ) e.Replacement = 'UTBackpack';
else if ( e.Replacee == 'FWeaponPiece1' ) e.Replacement = 'WarheadAmmo';
else if ( e.Replacee == 'CWeaponPiece1' ) e.Replacement = 'UTBackpack';
else if ( (e.Replacee == 'MWeaponPiece1') || (e.Replacee == 'MWeapBloodscourge') || (e.Replacee.GetClassName() == 'mkFullBloodscourge') ) e.Replacement = 'WarheadLauncher';
else if ( (e.Replacee == 'Mana1') || (e.Replacee is 'ArtiPoisonBag') ) e.Replacement = 'UTMinorAmmoBox';
else if ( e.Replacee == 'Mana2' ) e.Replacement = 'UTMediumAmmoBox';
else if ( e.Replacee == 'Mana3' ) e.Replacement = 'UTMajorAmmoBox';
else if ( e.Replacee == 'ArtiBoostMana' ) e.Replacement = 'ActUTFullAmmoBox';
// TODO Strife replacements
}
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_StartSound(snd,CHAN_BODY,CHANF_LOOPING,volume,attenuation);
return a;
}
override void WorldLoaded( WorldEvent e )
{
isbd = ispb = false;
for ( int i=0; i<AllActorClasses.size(); i++ )
{
if ( AllActorClasses[i].GetClassName() == "BrutalWeapon" )
isbd = true;
else if ( AllActorClasses[i].GetClassName() == "BrutalDoomer" )
ispb = true;
}
if ( isbd || ispb )
{
for ( int i=0; i<166; i++ )
gframes[i] = TexMan.CheckForTexture(String.Format("graphics/grunt/B_%03d.jpg",i+1),TexMan.Type_Any);
}
if ( level.levelname != "Modder Test Map" ) return;
// just replace the -noflat- with a better scaled version and change the sky
if ( !flak_doomtest )
{
Level.ReplaceTextures("-noflat-","DefTex",0);
TextureID skytx = TexMan.CheckForTexture("BlueSky",TexMan.Type_Any);
level.ChangeSky(skytx,skytx);
return;
}
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);
// prettify Kinsie's test map for a more Unreal feel
for ( int i=0; i<level.sectors.size(); i++ )
{
level.sectors[i].lightlevel = min(level.sectors[i].lightlevel,128);
if ( level.sectors[i].GetPlaneLight(0) ) level.sectors[i].SetPlaneLight(0,128);
if ( level.sectors[i].GetPlaneLight(1) ) level.sectors[i].SetPlaneLight(1,128);
// 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 )
{
if ( gameinfo.gametype&GAME_DOOM )
level.sectors[i].SetTexture(0,((i==47)||((i>=256)&&(i<=260)))?baseceil:baseflor);
else if ( gameinfo.gametype&GAME_HERETIC )
level.sectors[i].SetTexture(0,((i==47)||((i>=144)&&(i<=148)))?baseceil:baseflor);
else if ( gameinfo.gametype&GAME_HEXEN )
level.sectors[i].SetTexture(0,((i==47)||((i>=152)&&(i<=155)))?baseceil:baseflor);
level.sectors[i].SetXScale(0,2.);
level.sectors[i].SetYScale(0,2.);
}
if ( level.sectors[i].GetTexture(1) == deftex )
{
if ( gameinfo.gametype&GAME_DOOM )
level.sectors[i].SetTexture(1,((i==47)||((i>=256)&&(i<=260)))?baseflor:baseceil);
else if ( gameinfo.gametype&GAME_HERETIC )
level.sectors[i].SetTexture(1,((i==47)||((i>=144)&&(i<=148)))?baseflor:baseceil);
else if ( gameinfo.gametype&GAME_HEXEN )
level.sectors[i].SetTexture(1,((i==47)||((i>=152)&&(i<=155)))?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 ( ((gameinfo.gametype&GAME_DOOM) && ((i==529) || (i==530) || (i==533) || (i==534)))
|| ((gameinfo.gametype&GAME_RAVEN) && ((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
level.sectors[53].SetFade("00 00 20");
if ( gameinfo.gametype&GAME_DOOM )
{
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.sides[1844].SetTexture(1,glasstex);
level.lines[945].alpha = 0.5;
}
else if ( gameinfo.gametype&GAME_HERETIC )
{
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.sides[216].SetTexture(1,glasstex);
level.lines[125].alpha = 0.5;
}
else if ( gameinfo.gametype&GAME_HEXEN )
{
for ( int i=111; i<141; i++ )
{
if ( (i==114) || (i==117) || (i==123)
|| (i==126) || (i==128) || (i==134)
|| (i==139) ) continue;
level.sectors[i].SetSpecialColor(0,"00 00 00");
}
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);
if ( gameinfo.gametype&GAME_DOOM )
{
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);
}
else if ( gameinfo.gametype&GAME_HERETIC )
{
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);
}
else if ( gameinfo.gametype&GAME_HEXEN )
{
AddLight((1824,1760,64),"40 80 20",128);
AddLight((2080,1760,64),"FF 80 20",128);
AddLight((2336,1760,64),"20 20 80",128);
AddLight((2592,1760,64),"20 20 80",128);
AddLight((2944,960,64),"40 80 20",128);
AddLight((2944,736,64),"40 80 20",128);
AddLight((3264,960,64),"40 80 20",128);
AddLight((3264,736,64),"40 80 20",128);
AddLight((3264,512,64),"40 80 20",128);
AddLight((3264,288,64),"FF 80 20",128);
AddLight((3584,960,64),"40 80 20",128);
AddLight((3584,736,64),"FF 80 20",128);
AddLight((3584,512,64),"FF 80 20",128);
AddLight((3584,288,64),"FF 80 20",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);
if ( gameinfo.gametype&GAME_DOOM )
{
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);
}
else if ( gameinfo.gametype&GAME_HERETIC )
{
AddAmbient((2336,1760,64),"testamb/water",0.4,2.4);
AddAmbient((2592,1760,64),"testamb/lava",0.8,2.4);
AddAmbient((3264,288,64),"testamb/lava",0.8,2.4);
}
else if ( gameinfo.gametype&GAME_HEXEN )
{
AddAmbient((2080,1760,64),"testamb/lava",0.8,2.4);
AddAmbient((3264,288,64),"testamb/lava",0.8,2.4);
AddAmbient((3584,736,64),"testamb/lava",0.4,2.4);
AddAmbient((3584,288,64),"testamb/lava",0.4,2.4);
}
AddAmbient((3584,512,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?e.Inflictor.FindInventory("UTChainsaw"):null);
if ( !saw ) saw = Weapon(e.DamageSource?e.DamageSource.FindInventory("UTChainsaw"):null);
bool current = false;
if ( e.Inflictor && e.Inflictor.player && (e.Inflictor.player.ReadyWeapon == saw) ) current = true;
if ( e.DamageSource && 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.SetStateLabel("Null"); // instead of destroying, should prevent random VM aborts
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) || a.bCANNOTPUSH || (a.Mass >= 10000000) )
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 || !Victim.bSHOOTABLE || Victim.bCANNOTPUSH || (Victim.Mass >= 10000000) ) 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;
}
override void UiTick()
{
if ( !ispb && !isbd ) return;
if ( !(gametic%2) )
{
if ( !(gframe%11) && gframe < 160 )
{
S_StartSound("ut/malejump",CHAN_VOICE,CHANF_UI|CHANF_MAYBE_LOCAL,(gframe>100)?.5:1.);
S_StartSound("ut/malejump",CHAN_WEAPON,CHANF_UI|CHANF_MAYBE_LOCAL,(gframe>100)?.5:1.);
}
if ( ((gframe >= 37) && (gframe < 49) && !((gframe-37)%3))
|| ((gframe >= 89) && (gframe < 101) && !((gframe-89)%3))
|| ((gframe >= 141) && (gframe < 153) && !((gframe-141)%3)) )
{
S_StartSound("ut/land",CHAN_BODY,CHANF_UI|CHANF_MAYBE_LOCAL,(gframe>100)?.5:1.);
S_StartSound("ut/land",CHAN_ITEM,CHANF_UI|CHANF_MAYBE_LOCAL,(gframe>100)?.5:1.);
}
gframe++;
}
if ( gframe >= 166 ) gframe = 0;
}
// almost forgot about this
override void RenderUnderlay( RenderEvent e )
{
if ( !ispb && !isbd ) return;
double ar = Screen.GetAspectRatio();
Vector2 tsize = TexMan.GetScaledSize(gframes[gframe]);
double sar = tsize.x/tsize.y;
Vector2 vsize;
if ( sar > ar ) vsize = (tsize.y*ar,tsize.y);
else if ( sar < ar ) vsize = (tsize.x,tsize.x/ar);
else vsize = tsize;
Screen.DrawTexture(gframes[gframe],false,(vsize.x-tsize.x)/2,(vsize.y-tsize.y)/2,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true,DTA_LegacyRenderStyle,STYLE_Add);
String txtme = "STOP LOADING BRUTAL DOOM WITH EVERYTHING";
if ( !rfont ) rfont = Font.GetFont('USmallFont');
Vector2 bcoord = ((CleanWidth-rfont.StringWidth(txtme))*.5,CleanHeight*0.8);
for ( int i=0; i<txtme.Length(); i++ )
{
String chr = txtme.Mid(i,1);
for ( int j=0; j<8; j++ )
{
Vector2 ccoord = bcoord + (cos((gametic+e.fractic+i-j)*8.),sin((gametic+e.fractic+i-j)*8.))*CleanXFac*3;
Screen.DrawText(rfont,Font.CR_DARKRED,ccoord.x,ccoord.y,chr,DTA_VirtualWidth,CleanWidth,DTA_VirtualHeight,CleanHeight,DTA_KeepRatio,true,DTA_Alpha,(8-j)/8.);
}
bcoord.x += rfont.StringWidth(chr);
}
bcoord = ((CleanWidth-rfont.StringWidth(txtme))*.5,CleanHeight*0.8);
for ( int i=0; i<txtme.Length(); i++ )
{
String chr = txtme.Mid(i,1);
Vector2 ccoord = bcoord + (cos((gametic+e.fractic+i)*8.),sin((gametic+e.fractic+i)*8.))*CleanXFac*3;
Screen.DrawText(rfont,Font.CR_FIRE,ccoord.x,ccoord.y,chr,DTA_VirtualWidth,CleanWidth,DTA_VirtualHeight,CleanHeight,DTA_KeepRatio,true);
bcoord.x += rfont.StringWidth(chr);
}
}
}