Split player and utility code.

This commit is contained in:
Mari the Deer 2022-12-15 17:12:53 +01:00
commit 44f39d1024
26 changed files with 6908 additions and 6619 deletions

View file

@ -1,3 +1,3 @@
[default]
SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r693 \cu(Thu 15 Dec 14:31:43 CET 2022)\c-";
SWWM_SHORTVER="\cw1.3pre r693 \cu(2022-12-15 14:31:43)\c-";
SWWM_MODVER="\cyDEMOLITIONIST \cw1.3pre r694 \cu(Thu 15 Dec 17:12:53 CET 2022)\c-";
SWWM_SHORTVER="\cw1.3pre r694 \cu(2022-12-15 17:12:53)\c-";

View file

@ -11,20 +11,38 @@ version "4.10"
#include "zscript/swwm_Gutamatics/Include.zsc"
// ZPolyobject
#include "zscript/swwm_Polyobjects/Polyobjects.zs"
// utility code
#include "zscript/utility/swwm_utility.zsc"
#include "zscript/utility/swwm_utility_achievements.zsc"
#include "zscript/utility/swwm_utility_blast.zsc"
#include "zscript/utility/swwm_utility_compat.zsc"
#include "zscript/utility/swwm_utility_info.zsc"
#include "zscript/utility/swwm_utility_item.zsc"
#include "zscript/utility/swwm_utility_map.zsc"
#include "zscript/utility/swwm_utility_math.zsc"
#include "zscript/utility/swwm_utility_string.zsc"
// base code
#include "zscript/swwm_utility.zsc"
#include "zscript/swwm_common.zsc"
#include "zscript/swwm_common_fx.zsc"
#include "zscript/swwm_handler.zsc"
#include "zscript/swwm_statichandler.zsc"
#include "zscript/swwm_thinkers.zsc"
#include "zscript/swwm_thinkers_player.zsc"
#include "zscript/swwm_player.zsc"
#include "zscript/swwm_player_fx.zsc"
#include "zscript/swwm_player_items.zsc"
#include "zscript/swwm_gesture.zsc"
#include "zscript/swwm_gesture_fx.zsc"
#include "zscript/swwm_blod.zsc"
// player code
#include "zscript/player/swwm_player.zsc"
#include "zscript/player/swwm_player_anim.zsc"
#include "zscript/player/swwm_player_cheats.zsc"
#include "zscript/player/swwm_player_extra.zsc"
#include "zscript/player/swwm_player_face.zsc"
#include "zscript/player/swwm_player_fx.zsc"
#include "zscript/player/swwm_player_inventory.zsc"
#include "zscript/player/swwm_player_items.zsc"
#include "zscript/player/swwm_player_move.zsc"
#include "zscript/player/swwm_player_think.zsc"
#include "zscript/player/swwm_player_tick.zsc"
// handler code
#include "zscript/handler/swwm_handler_cheats.zsc"
#include "zscript/handler/swwm_handler_crosshair.zsc"

View file

@ -27,7 +27,7 @@ extend Class SWWMHandler
e.Thing.vel *= 2;
Vector3 x, y, z;
double ang = e.Thing.target.target?e.Thing.AngleTo(e.Thing.target.target):e.Thing.angle;
double pt = e.Thing.target.target?SWWMUtility.PitchTo(e.Thing,e.Thing.target.target,.5):e.Thing.pitch;
double pt = e.Thing.target.target?e.Thing.PitchTo(e.Thing.target.target,e.Thing.target.missileheight,e.Thing.target.target.Height/2.):e.Thing.pitch;
[x, y, z] = SWWMUtility.GetAxes(ang,pt,e.Thing.roll);
int numpt = Random[ExtraMissiles](1,2);
for ( int i=0; i<numpt; i++ )

View file

@ -438,7 +438,7 @@ Class MykradvoBurst : SWWMNonInteractiveActor
{
let t = Spawn("MykradvoTendril",pos);
t.angle = t.AngleTo(targ);
t.pitch = SWWMUtility.PitchTo(t,targ,.5);
t.pitch = t.PitchTo(targ,0,targ.Height/2);
t.target = target;
t.tracer = targ;
}

View file

@ -0,0 +1,763 @@
// The Demolitionist
Enum EDemoFaceState
{
FS_DEFAULT,
FS_EVIL,
FS_GRIN,
FS_WINK,
FS_BLINK,
FS_SAD,
FS_PAIN,
FS_OUCH,
FS_DEAD // UNUSED
};
Class Demolitionist : PlayerPawn
{
int last_jump_held, last_boost, last_kick;
Vector3 dashdir;
double dashfuel, dashboost;
int dashcooldown, boostcooldown, fuelcooldown;
int dashlockst;
bool fullfuel;
bool sendtoground;
bool key_reentrant;
bool bInDefaultInventory;
bool oldsinglefirst;
transient int lastdamage;
transient int lastdamagetic, lastdamagetimer;
bool lastground;
int lastgroundtic, lastairtic;
double lastvelz, prevvelz, landvelz;
double ssup;
transient CVar myvoice;
SWWMStats mystats;
int cairtime;
bool hasteleported;
bool hasrevived;
int lastmpain;
double guideangle, guidepitch, guideroll;
// for weapon bobbing stuff
double bumpvelz, bumpangle, bumppitch, bumproll;
double oldangle, oldpitch;
double oldlagangle, oldlagpitch, oldlagready;
Vector3 oldlagvel;
double lagangle, lagpitch, lagready;
Vector3 lagvel;
enum EUnderType
{
UNDER_NONE,
UNDER_WATER,
UNDER_SLIME,
UNDER_LAVA
};
int lastunder;
Color undercol;
int deadtimer;
transient int revivefail;
transient int bumptic;
transient double lastbump;
transient CVar bumpstr;
DemolitionistSelfLight selflight;
Actor oldencroached;
Vector3 oldencroachedpos;
int encroachtics;
Vector3 pretelepos;
SWWMItemSense itemsense;
int healcooldown, healtimer, oldhealth;
bool scriptedinvul;
bool hitactivate;
Actor froggy;
transient int lastuse, failcounter, failcooldown;
transient SWWMItemTracer itrace;
bool meleeuse;
transient bool bWalking;
int airscreamtime;
enum EInvWipe
{
WIPE_EPISODE = 1,
WIPE_CLUSTER = 2,
WIPE_MAP = 4
}
int invwipe; // inventory wipe flags for next level
transient int lastbang, lastbust;
transient bool ingivecheat;
Property DashFuel : dashfuel;
EDemoFaceState facestate;
int paindir;
int facetimer;
int blinktime;
transient int oldfaceidx;
transient int rss;
transient bool facedamage, facegrin, facesad, facewink, faceblink;
transient CVar tagcolor;
int oldtagcolor;
transient int magtime;
SWWMMagItem magitem;
int magitem_cnt;
SWWMShadow myshadow;
double bobtime, oldbobtime, oldbob;
Default
{
Tag "$T_DEMOLITIONIST";
Speed 1;
Radius 16; // should be 9 in theory, but it'd be too thin
Height 56;
Mass 500;
PainChance 255;
MaxSlopeSteepness 0; // mountain goat mode, all slopes are walkable
Player.DisplayName "$T_DEMOLITIONIST";
// StartItem array is defined but not used directly
// just declared here for mod compat
Player.StartItem "ExplodiumGun";
Player.StartItem "DeepImpact";
Player.StartItem "AlmasteelPlating";
Player.StartItem "SayaCollar";
Player.ViewHeight 52;
Player.AirCapacity 0;
Player.GruntSpeed 20;
Player.ForwardMove 1., 1.;
Player.SideMove 1., 1.;
Player.SoundClass "demolitionist";
DamageFactor "Drowning", 0.;
DamageFactor "Poison", 0.;
DamageFactor "PoisonCloud", 0.;
DamageFactor "Falling", 0.;
Demolitionist.DashFuel 240.;
+NOBLOOD;
+DONTGIB;
+NOICEDEATH;
+NOSKIN;
+DONTMORPH;
+DONTDRAIN;
+DONTCORPSE;
-WINDTHRUST; // too heavy
}
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( inflictor && inflictor.FindInventory("ParriedBuff") ) return StringTable.Localize("$O_PARRY");
if ( mod == 'Jump' ) return StringTable.Localize("$O_JUMP");
if ( mod == 'Dash' ) return StringTable.Localize("$O_DASH");
if ( mod == 'Buttslam' ) return StringTable.Localize("$O_BUTT");
if ( mod == 'GroundPound' ) return StringTable.Localize("$O_POUND");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override double GetDeathHeight()
{
double basedeathheight = Super.GetDeathHeight();
// limit death height to crouch height, to avoid dying players from getting stuck on revive
return max(Height*.3,basedeathheight);
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
lastground = true; // prevent sudden landing sound on map start
blinktime = 30;
// swap ourselves for a voodoo doll
if ( !player || (player.mo != self) )
{
let v = Spawn("SWWMVoodooDoll",pos);
v.angle = angle;
v.player = player;
// give it a face if it belongs to a player
if ( player )
{
v.bFRIENDLY = true;
v.A_ChangeModel("",0,"","",1,"models","VoodooDollFace.png",CMDL_USESURFACESKIN,-1);
}
Destroy();
return;
}
oldsinglefirst = swwm_singlefirst; // super already sets up the slots, so save the cvar value now
mystats = SWWMStats.Find(player);
// sanity checks
if ( !EventHandler.Find("SWWMHandler") || !StaticEventHandler.Find("SWWMStaticHandler") )
ThrowAbortException("Panic! SWWM event handlers not detected!");
}
void BumpView( double str, Vector3 dir = (0,0,0) )
{
double dirlen = dir.length();
if ( dirlen < double.epsilon )
{
bumppitch += str;
return;
}
dir /= dirlen;
Vector3 x, y;
Quat r = Quat.FromAngles(angle+viewangle,pitch+viewpitch,roll+viewroll);
x = r*(1,0,0);
y = r*(0,-1,0);
double sx = dir dot x;
double sy = dir dot y;
if ( !sx && !sy ) bumppitch += str;
else
{
bumppitch += str*sx;
bumproll += str*sy;
}
}
override void CheckFOV()
{
if ( !player ) return;
float desired = player.desiredfov;
// adjust fov from weapon (abs due to special use of negative
// to prevent it from scaling look sensitivity)
if ( (player.playerstate != PST_DEAD) && player.readyweapon
&& player.readyweapon.fovscale )
desired *= abs(player.readyweapon.fovscale);
// additional fov bump from various effects
// akin to the old A_ZoomFactor trick, but not limited to weapons and can stack
if ( lastbump <= 0. ) lastbump = 1.;
if ( lastbump != 1. )
{
if ( !bumpstr ) bumpstr = CVar.GetCVar('swwm_bumpstrength',player);
double str = bumpstr.GetFloat();
player.fov *= lastbump*str+1.-str;
lastbump = 1.;
}
// adjust fov from dashing
double spd = vel.length();
if ( InStateSequence(CurState,FindState("Dash")) && (spd > 10.) )
{
Vector3 facedir = SWWMUtility.Vec3FromAngles(angle,pitch);
if ( spd > 0. )
{
double rel = max(0,vel.unit() dot facedir);
desired *= 1.+clamp(rel*(spd-10.),-80.,80.)*.002;
}
}
if ( player.fov == desired ) return;
// interpolate towards desired fov
if ( abs(player.fov-desired) < .1 ) player.fov = desired;
else
{
float zoom = max(.1,abs(player.fov-desired)*.35);
if ( player.fov > desired ) player.fov -= zoom;
else player.fov += zoom;
}
}
override void CheckPoison()
{
// HAHA no
player.poisoncount = 0;
}
private void CheckBreakCrusher()
{
double gaph = (ceilingz-floorz);
if ( gaph > height*.8 ) return;
// the smaller the gap, the more likely the crusher will snap
if ( Random[Demolitionist](0,2) && (FRandom[Demolitionist](0,gaph/height) > .2) ) return;
double diffh = 8.+(default.height-gaph); // how much the crusher will have to "snap" after breaking
let ceil = ceilingsector;
let flor = floorsector;
let ceilse = ceil.ceilingdata;
let florse = flor.floordata;
if ( ceilse && florse )
{
// snap both planes
let q = Spawn("BustedQuake",(ceil.centerspot.x,ceil.centerspot.y,ceil.ceilingplane.ZAtPoint(ceil.centerspot)));
q.special1 = 6;
q = Spawn("BustedQuake",(flor.centerspot.x,flor.centerspot.y,flor.floorplane.ZAtPoint(flor.centerspot)));
q.special1 = 6;
SWWMCrusherBroken.Create(flor,ceil,diffh/2.);
}
else if ( ceilse )
{
// snap ceiling
let q = Spawn("BustedQuake",(ceil.centerspot.x,ceil.centerspot.y,ceil.ceilingplane.ZAtPoint(ceil.centerspot)));
q.special1 = 10;
SWWMCrusherBroken.Create(null,ceil,diffh);
}
else if ( florse )
{
// snap floor
let q = Spawn("BustedQuake",(flor.centerspot.x,flor.centerspot.y,flor.floorplane.ZAtPoint(flor.centerspot)));
q.special1 = 10;
SWWMCrusherBroken.Create(flor,null,diffh);
}
SWWMUtility.MarkAchievement("crush",player);
}
private void CheckBreakPolyobject( int dmg )
{
// see if there are any crushing polyobjects currently "encroaching" the player
Array<Line> touching;
BlockLinesIterator bl = BlockLinesIterator.Create(self,radius+8);
double tbox[4];
// top, bottom, left, right
tbox[0] = pos.y+(radius+8);
tbox[1] = pos.y-(radius+8);
tbox[2] = pos.x-(radius+8);
tbox[3] = pos.x+(radius+8);
while ( bl.Next() )
{
Line l = bl.CurLine;
if ( !l ) continue;
if ( tbox[2] > l.bbox[3] ) continue;
if ( tbox[3] < l.bbox[2] ) continue;
if ( tbox[0] < l.bbox[1] ) continue;
if ( tbox[1] > l.bbox[0] ) continue;
if ( SWWMUtility.BoxOnLineSide(tbox[0],tbox[1],tbox[2],tbox[3],l) != -1 ) continue;
touching.Push(l);
}
let pi = swwm_PolyobjectIterator.Create();
swwm_PolyobjectHandle p;
while ( p = pi.Next() )
{
if ( (p.Type != swwm_PolyobjectHandle.POTYP_CRUSH) && (p.Type != swwm_PolyobjectHandle.POTYP_HURT) )
continue;
foreach ( l:touching )
{
if ( p.Lines.Find(l) >= p.Lines.Size() ) continue;
Vector2 diragainst = pos.xy-p.GetPos();
double vsiz = diragainst.length();
if ( vsiz > 0 ) diragainst /= vsiz;
if ( BusterWall.BustPolyobj(p,max(dmg,(100-health)*5),self,(diragainst.x,diragainst.y,0)) )
SWWMUtility.MarkAchievement("crush",player);
if ( p.Mirror && BusterWall.BustPolyobj(p.Mirror,max(dmg,(100-health)*5),self,-(diragainst.x,diragainst.y,0)) )
SWWMUtility.MarkAchievement("crush",player);
}
}
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// we still have to ENSURE ENTIRELY that this gets nullified (TELEFRAG_DAMAGE overrides damage factors somehow)
if ( (mod == 'Falling') | (mod == 'Drowning') || (mod == 'Poison') || (mod == 'PoisonCloud') )
damage = 0;
if ( (swwm_strictuntouchable >= 2) && (damage > 0) && player )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd ) hnd.tookdamage[PlayerNumber()] = true;
}
if ( (mod == 'Crush') && player && (player.mo == self) )
{
// check if we can break any active crushers
// (or polyobjects)
if ( !inflictor && !source )
{
CheckBreakCrusher();
CheckBreakPolyobject(damage);
}
// break a spike trap
else if ( source is 'ThrustFloor' )
{
let q = Spawn("BustedQuake",source.pos);
q.special1 = 4;
int numpt = Random[ExploS](30,40);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("SWWMChip",source.Vec3Angle(source.radius,FRandom[ExploS](0,360),1.+FRandom[ExploS](0,max(0.,source.height-source.floorclip))));
s.vel = ((0,0,1)+SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*.6).unit()*FRandom[ExploS](2.,16.);
s.scale *= FRandom[ExploS](1.5,3.);
s.A_SetTranslation('StoneSpike');
}
numpt = Random[ExploS](12,16);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("SWWMHalfSmoke",source.Vec3Offset(0,0,1.+FRandom[ExploS](0,max(0.,source.height-source.floorclip))));
s.vel = ((0,0,1)+SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))).unit()*FRandom[ExploS](-2,8.);
s.scale *= 2.5;
s.special1 = Random[ExploS](3,8);
s.SetShade(Color(5,4,3)*Random[ExploS](20,40));
}
Spawn("SWWMCrushedSpike",source.pos);
if ( source.master ) source.master.Destroy();
source.Destroy();
damage = 20; // reduce so it's not instant kill
SWWMUtility.MarkAchievement("kancho",player);
}
}
// no damage whatsoever
if ( scriptedinvul )
return 0;
if ( damage <= 0 ) return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
if ( !inflictor && !source && (FloorSector.flags&Sector.SECF_ENDLEVEL) )
{
// end level hax
damage = max(50,health-100);
flags |= DMG_FORCED|DMG_NO_ARMOR;
mod = 'EndLevel';
}
int oldpchance = PainChance;
if ( damage < 5 ) PainChance = 0;
int realdmg = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
if ( lastdamagetic != gametic )
{
lastdamage = 0;
lastdamagetimer = 0;
}
lastdamage += realdmg;
lastdamagetic = gametic;
lastdamagetimer = max(lastdamagetimer,lastdamagetic+clamp(lastdamage/2,10,40));
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
{
lastmpain = level.maptime;
if ( player && (player.mo == self) ) A_DemoPain();
}
PainChance = oldpchance;
if ( (Health <= 0) && (source == self) && (flags&DMG_EXPLOSION) )
SWWMUtility.MarkAchievement("dime",player);
facedamage = true;
return realdmg;
}
private Vector2 BobWeaponAngle( double ticfrac )
{
// bob with player view
Vector2 prevbob = (-sin(oldbobtime*180.),.5*abs(sin(oldbobtime*180.)))*oldbob*.25;
Vector2 bob = (-sin(bobtime*180.),.5*abs(sin(bobtime*180.)))*player.bob*.25;
double bobstr = (player.WeaponState&WF_WEAPONBOBBING)?1.:player.GetWBobFire();
return SWWMUtility.LerpVector2(prevbob,bob,ticfrac)*bobstr*clamp(viewbob,0.,1.5);
}
override Vector2 BobWeapon( double ticfrac )
{
// non-mod weapons bob normally
if ( !(player.ReadyWeapon is 'SWWMWeapon') )
return Super.BobWeapon(ticfrac);
bool oldbob = !!(player.WeaponState&WF_WEAPONBOBBING);
player.WeaponState |= WF_WEAPONBOBBING; // always bob
Vector2 cur = BobWeaponAngle(ticfrac);
if ( !oldbob ) player.WeaponState &= ~WF_WEAPONBOBBING;
double fangle = SWWMUtility.Lerp(oldangle,angle,ticfrac);
double fpitch = SWWMUtility.Lerp(oldpitch,pitch,ticfrac);
double flagangle = SWWMUtility.Lerp(oldlagangle,lagangle,ticfrac);
double flagpitch = SWWMUtility.Lerp(oldlagpitch,lagpitch,ticfrac);
double diffang = fangle-flagangle;
double diffpitch = fpitch-flagpitch;
if ( abs(diffang) > 1. )
{
int sgn = (diffang>0)?1:-1;
diffang = abs(diffang)**.7*sgn;
}
if ( abs(diffpitch) > 1. )
{
int sgn = (diffpitch>0)?1:-1;
diffpitch = abs(diffpitch)**.7*sgn;
}
Vector3 flagvel = SWWMUtility.LerpVector3(oldlagvel,lagvel,ticfrac);
double diffx = flagvel dot SWWMUtility.AngleToVector3(flagangle+90);
double diffy = flagvel dot (0,0,1);
if ( abs(diffx) > 1. )
{
int sgn = (diffx>0)?1:-1;
diffx = abs(diffx)**.5*sgn;
}
if ( abs(diffy) > 1. )
{
int sgn = (diffy>0)?1:-1;
diffy = abs(diffy)**.5*sgn;
}
cur.x += diffang*.4;
cur.y -= diffpitch*.4;
cur.x += diffx*.8;
cur.y += diffy*.8;
return cur*SWWMUtility.Lerp(oldlagready,lagready,ticfrac);
}
override bool OnGiveSecret( bool printmsg, bool playsound )
{
if ( !player ) return false;
int score = 100;
// last secret (this is called before counting it up, so have to subtract)
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !deathmatch && !(gameinfo.gametype&GAME_Hexen) && (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) )
{
if ( hnd ) hnd.allsecrets = true;
score = 1000;
if ( player == players[consoleplayer] )
{
Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),score);
SWWMHandler.AddOneliner("findsecret",2,40);
facegrin = true;
}
else Console.Printf(StringTable.Localize("$SWWM_LASTSECRETREM"),player.GetUserName(),score);
SWWMUtility.AchievementProgressInc("allsecrets",1,player);
}
else if ( player == players[consoleplayer] )
{
Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),score);
SWWMHandler.AddOneliner("findsecret",2,40);
facegrin = true;
}
else Console.Printf(StringTable.Localize("$SWWM_FINDSECRETREM"),player.GetUserName(),score);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(score,Vec3Offset(0,0,Height/2));
// somehow ongivesecret can be called BEFORE PostBeginPlay (what the fuck)
if ( !mystats ) mystats = SWWMStats.Find(player);
mystats.secrets++;
return true;
}
override bool Used( Actor user )
{
if ( !(user is 'Demolitionist') || !player || (player.mo != self) ) return false;
if ( (user.player == players[consoleplayer]) && (health > 0) )
{
SWWMHandler.AddOneliner("greet",2);
return true;
}
return false;
}
override void PreTravelled()
{
// clean up attached actors
if ( selflight ) selflight.Destroy();
if ( myshadow ) myshadow.Destroy();
// clean up our objects
SWWMItemSense si = itemsense;
while ( si )
{
let next = si.next;
si.Destroy();
si = next;
}
SWWMMagItem mi = magitem;
while ( mi )
{
let next = mi.next;
mi.Destroy();
mi = next;
}
magitem_cnt = 0;
// disable death exits
if ( player && (player.playerstate == PST_DEAD) )
{
player.cheats &= ~(CF_FROZEN|CF_TOTALLYFROZEN);
player.Resurrect();
player.damagecount = 0;
player.bonuscount = 0;
player.poisoncount = 0;
roll = 0;
if ( special1 > 2 ) special1 = 0;
}
// inventory wipes
if ( invwipe && (player.playerstate != PST_DEAD) )
{
bool wiped = false;
bool resetammo = false;
bool resetitems = false;
bool resethealth = false;
if ( invwipe&WIPE_EPISODE )
{
SWWMUtility.WipeInventory(self,swwm_resetscore);
wiped = true;
}
if ( invwipe&WIPE_CLUSTER )
{
if ( (swwm_ps_fullreset == 2) && !wiped )
{
SWWMUtility.WipeInventory(self,swwm_resetscore);
wiped = true;
}
if ( (swwm_ps_resetammo == 2) && !wiped )
{
SWWMUtility.ResetAmmo(self);
resetammo = true;
}
if ( (swwm_ps_resetitems == 2) && !wiped )
{
SWWMUtility.ResetItems(self);
resetitems = true;
}
if ( (swwm_ps_resethealth == 2) && !wiped )
{
SWWMUtility.ResetHealth(self);
resethealth = true;
}
}
if ( invwipe&WIPE_MAP )
{
if ( (swwm_ps_fullreset == 1) && !wiped )
{
SWWMUtility.WipeInventory(self,swwm_resetscore);
wiped = true;
}
if ( (swwm_ps_resetammo == 1) && !wiped && !resetammo )
SWWMUtility.ResetAmmo(self);
if ( (swwm_ps_resetitems == 1) && !wiped && !resetitems )
SWWMUtility.ResetItems(self);
if ( (swwm_ps_resethealth == 1) && !wiped && !resethealth )
SWWMUtility.ResetHealth(self);
}
}
invwipe = 0;
}
override void Travelled()
{
// reinitialize
dashfuel = default.dashfuel;
last_boost = 0;
last_kick = 0;
hasrevived = false;
blinktime = 30;
// cancel dash/boost
A_StopSound(CHAN_JETPACK);
fuelcooldown = 0.;
dashcooldown = 0.;
dashboost = 0.;
// prevent sudden stomping if we were previously falling
lastvelz = vel.z;
// early cancel gestures
if ( player )
{
if ( player.ReadyWeapon is 'SWWMItemGesture' )
player.ReadyWeapon = SWWMItemGesture(player.ReadyWeapon).gest;
if ( player.ReadyWeapon is 'SWWMGesture' )
{
player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon;
player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect"));
}
}
// re-add ourselves to the "suckable list" (otherwise the Ynykron Singularity won't hurt us)
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd && (hnd.SuckableActors.Find(self) >= hnd.SuckableActors.Size()) )
hnd.SuckableActors.Push(self);
}
override bool PreTeleport( Vector3 destpos, double destangle, int flags )
{
// store old pos
pretelepos = pos;
return true;
}
override void PostTeleport( Vector3 destpos, double destangle, int flags )
{
hasteleported = true; // notify tick that we teleported, so it ignores the travel distance
mystats.teledist += level.Vec3Diff(pretelepos,pos).length();
// reset all smooth bob variables if angles/velocity aren't carried over
if ( !(flags&TELF_KEEPORIENTATION) )
{
oldlagangle = lagangle = oldangle = angle;
oldlagpitch = lagpitch = oldpitch = pitch;
}
if ( !(flags&TELF_KEEPVELOCITY) )
{
oldlagvel = lagvel = vel;
lastvelz = vel.z;
}
// notify carried lamp that we just moved
let l = SWWMLamp(FindInventory("SWWMLamp"));
if ( l && l.thelamp )
CompanionLamp(l.thelamp).justteleport = true;
}
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkSound("demolitionist/walk1");
MarkSound("demolitionist/walk2");
MarkSound("demolitionist/walk3");
MarkSound("demolitionist/walk4");
MarkSound("demolitionist/runstart1");
MarkSound("demolitionist/runstart2");
MarkSound("demolitionist/runstart3");
MarkSound("demolitionist/runstart4");
MarkSound("demolitionist/run1");
MarkSound("demolitionist/run2");
MarkSound("demolitionist/run3");
MarkSound("demolitionist/run4");
MarkSound("demolitionist/runstop1");
MarkSound("demolitionist/runstop2");
MarkSound("demolitionist/runstop3");
MarkSound("demolitionist/runstop4");
MarkSound("demolitionist/jet");
MarkSound("demolitionist/jetstop");
MarkSound("demolitionist/death1");
MarkSound("demolitionist/death2");
MarkSound("demolitionist/death3");
MarkSound("demolitionist/xdeath1");
MarkSound("demolitionist/xdeath2");
MarkSound("demolitionist/xdeath3");
MarkSound("demolitionist/wdeath1");
MarkSound("demolitionist/wdeath2");
MarkSound("demolitionist/wdeath3");
MarkSound("demolitionist/pain1");
MarkSound("demolitionist/pain2");
MarkSound("demolitionist/pain3");
MarkSound("demolitionist/hipain1");
MarkSound("demolitionist/hipain2");
MarkSound("demolitionist/hipain3");
MarkSound("demolitionist/lopain1");
MarkSound("demolitionist/lopain2");
MarkSound("demolitionist/lopain3");
MarkSound("demolitionist/hardland1");
MarkSound("demolitionist/hardland2");
MarkSound("demolitionist/hardland3");
MarkSound("demolitionist/swing1");
MarkSound("demolitionist/swing2");
MarkSound("demolitionist/swing3");
MarkSound("demolitionist/wswing1");
MarkSound("demolitionist/wswing2");
MarkSound("demolitionist/punch1");
MarkSound("demolitionist/punch2");
MarkSound("demolitionist/punch3");
MarkSound("demolitionist/punchf1");
MarkSound("demolitionist/punchf2");
MarkSound("demolitionist/punchf3");
MarkSound("demolitionist/bump1");
MarkSound("demolitionist/bump2");
MarkSound("demolitionist/bump3");
MarkSound("demolitionist/kick1");
MarkSound("demolitionist/kick2");
MarkSound("demolitionist/kick3");
MarkSound("demolitionist/revive");
MarkSound("demolitionist/youdied");
MarkSound("demolitionist/parry");
MarkSound("demolitionist/handsup");
MarkSound("demolitionist/handsdown");
MarkSound("demolitionist/whits1");
MarkSound("demolitionist/whits2");
MarkSound("demolitionist/whits3");
MarkSound("demolitionist/whitm1");
MarkSound("demolitionist/whitm2");
MarkSound("demolitionist/whitm3");
MarkSound("demolitionist/whitl1");
MarkSound("demolitionist/whitl2");
MarkSound("demolitionist/buttslam");
MarkSound("demolitionist/buttslamx");
MarkSound("demolitionist/smooch");
MarkSound("demolitionist/blowkiss");
MarkSound("demolitionist/petting");
MarkSound("demolitionist/knockout");
}
}

View file

@ -0,0 +1,954 @@
// animations and states
extend Class Demolitionist
{
override void PlayIdle()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("Spawn")) )
SetStateLabel("Spawn");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("Crouch");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("Float")) )
{
SetStateLabel("Spawn");
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
}
else if ( InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop")) )
SetStateLabel("SeeFastEnd");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
}
}
else if ( !bNoGravity )
{
// Falling
if ( player.crouchdir == -1 )
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
}
else
{
if ( (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Float")))
&& (abs(pos.z-floorz) > maxstepheight) )
SetStateLabel("Fall");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
}
}
else
{
// Swimming
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop")) )
SetStateLabel("Float");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
}
}
}
override void PlayRunning()
{
if ( !player )
{
if ( !InStateSequence(CurState,FindState("SeeRun")) )
SetStateLabel("SeeRun");
return;
}
if ( player.health <= 0 ) return;
if ( !bNoGravity && player.onground )
{
// Ground
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
{
switch( FastCheck() )
{
case 2:
SetStateLabel("CrouchMoveFast");
break;
case 1:
SetStateLabel("CrouchMoveRun");
break;
default:
SetStateLabel("CrouchMove");
break;
}
}
}
else
{
if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
else if ( InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("SwimEnd");
else if ( (FastCheck() == 2)
&& (InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))) )
SetStateLabel("SeeFast");
else if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Turn")) )
{
if ( FastCheck() == 1 ) SetStateLabel("SeeRun");
else SetStateLabel("See");
A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
}
}
}
else if ( !bNoGravity )
{
// Falling
PlayIdle();
}
else
{
// Swimming
if ( player.crouchdir == -1 )
{
// Crouching
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("StartCrouch");
else if ( InStateSequence(CurState,FindState("Crouch")) )
{
switch( FastCheck() )
{
case 2:
SetStateLabel("CrouchMoveFast");
break;
case 1:
SetStateLabel("CrouchMoveRun");
break;
default:
SetStateLabel("CrouchMove");
break;
}
}
}
else if ( bFlyCheat || (player.cheats&CF_NOCLIP2) )
{
// Special case, fly cheats don't play a swim animation, only float
// (this fixes Demo "swimming" on the library ladder in Spooktober, for example)
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Swim"))
|| InStateSequence(CurState,FindState("SwimLoop"))
|| InStateSequence(CurState,FindState("SwimLoopRun"))
|| InStateSequence(CurState,FindState("SwimLoopFast")) )
SetStateLabel("Float");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMove"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
}
else
{
if ( InStateSequence(CurState,FindState("Spawn"))
|| InStateSequence(CurState,FindState("Turn"))
|| InStateSequence(CurState,FindState("See"))
|| InStateSequence(CurState,FindState("SeeRun"))
|| InStateSequence(CurState,FindState("SeeFast"))
|| InStateSequence(CurState,FindState("SeeFastLoop"))
|| InStateSequence(CurState,FindState("SeeFastEnd"))
|| InStateSequence(CurState,FindState("Jump"))
|| InStateSequence(CurState,FindState("Fall"))
|| InStateSequence(CurState,FindState("FallLoop"))
|| InStateSequence(CurState,FindState("Float")) )
SetStateLabel("Swim");
else if ( InStateSequence(CurState,FindState("Crouch"))
|| InStateSequence(CurState,FindState("CrouchMoveRun"))
|| InStateSequence(CurState,FindState("CrouchMoveFast")) )
SetStateLabel("EndCrouch");
}
}
}
override void PlayAttacking()
{
// Do nothing if it's a SWWM weapon, since those do things themselves
if ( player && (player.ReadyWeapon is 'SWWMWeapon') )
return;
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
override void PlayAttacking2()
{
PlayAttacking();
}
void PlayFire()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile");
else SetStateLabel("Missile");
}
void PlayMelee()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMelee");
else SetStateLabel("Melee");
}
void PlayFastMelee()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastMelee");
else SetStateLabel("FastMelee");
}
void PlayReload()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchReload");
else SetStateLabel("Reload");
}
void PlayFastReload()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastReload");
else SetStateLabel("FastReload");
}
void PlayCheckGun()
{
if ( InStateSequence(CurState,FindState("Dash"))
|| InStateSequence(CurState,FindState("Boost")) )
return; // don't cancel dash/boost
if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchCheckGun");
else SetStateLabel("CheckGun");
}
void A_DMFade()
{
if ( player ) return;
Spawn("TeleportFog",pos,ALLOW_REPLACE);
Destroy();
}
void A_DemoPain()
{
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
int loudlv = swwm_voiceamp;
if ( lastdamage > 70 )
{
A_QuakeEx(3,3,3,9,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_StartSound("demolitionist/hipain",CHAN_VOICE);
lastbump *= 1.04;
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
else if ( lastdamage > 30 )
{
A_QuakeEx(2,2,2,6,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_StartSound("demolitionist/pain",CHAN_VOICE);
lastbump *= 1.02;
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
else if ( lastdamage > 0 )
{
A_QuakeEx(1,1,1,3,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_StartSound("demolitionist/lopain",CHAN_VOICE);
lastbump *= 1.01;
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
}
void A_DemoScream()
{
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
A_StopSound(CHAN_DEMOVOICE);
A_StopSound(CHAN_DEMOVOICEAUX);
A_StopSound(CHAN_DEMOVOICEAUX2);
A_StopSound(CHAN_DEMOVOICEAUX3);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
int loudlv = swwm_voiceamp;
Sound snd = "demolitionist/death";
if ( special1 < 10 )
snd = "demolitionist/wdeath";
if ( health < -50 )
snd = "demolitionist/xdeath";
A_StartSound(snd,CHAN_VOICE);
if ( swwm_mutevoice < 4 )
{
A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
}
void A_Dash()
{
vel += dashdir*dashboost*clamp(dashfuel/20.,0.,1.);
player.vel *= 0.;
if ( dashboost < .2 ) dashboost = 0.;
else if ( !(player.cmd.buttons&BT_USER2) ) dashboost *= .1;
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
SWWMUtility.AchievementProgressIncDouble("fuel",fueluse,player);
mystats.fuelusage += fueluse;
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
dashcooldown = min(40,max(10,int(dashcooldown*1.4)));
dashlockst = clamp(dashcooldown/3,2,10);
fuelcooldown = max(30,fuelcooldown);
if ( (dashfuel <= 0.) && fullfuel )
SWWMUtility.AchievementProgressInc("brake",1,player);
if ( (dashfuel <= 0.) || (dashboost <= 0.) )
SetStateLabel("DashEnd");
}
void A_BoostUp( bool initial = false )
{
vel.z += .25*dashboost*clamp(dashfuel/20.,0,1.);
player.vel *= 0.;
if ( dashboost < .2 ) dashboost = 0.;
else
{
if ( player.cmd.buttons&BT_JUMP ) dashboost = min(5.,dashboost*1.01);
else dashboost *= .4;
last_boost = level.maptime+1;
}
double fueluse = (dashfuel-max(0.,dashfuel-dashboost))/60.;
SWWMUtility.AchievementProgressIncDouble("fuel",fueluse,player);
mystats.fuelusage += fueluse;
if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost);
if ( ((dashfuel <= 0.) || (dashboost <= 0.)) )
{
if ( !initial )
{
if ( player.onground ) SetStateLabel("JumpEnd");
else SetStateLabel("Fall");
}
return;
}
fuelcooldown = max(20,fuelcooldown);
}
void A_Footstep( double yofs, int run = 0, double vol = .3, bool nosplash = false )
{
if ( run == 2 )
{
A_StartSound("demolitionist/run",CHAN_FOOTSTEP,CHANF_OVERLAP,vol);
if ( !nosplash )
{
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/3600.)*TweakSpeed();
}
else if ( run == 1 )
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.5);
if ( !nosplash )
{
let b = Spawn("InvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/4800.)*TweakSpeed();
}
else
{
A_StartSound("demolitionist/walk",CHAN_FOOTSTEP,CHANF_OVERLAP,vol*.2);
if ( !nosplash )
{
let b = Spawn("SmolInvisibleSplasher",level.Vec3Offset(pos,(RotateVector((0,yofs*.25*radius),angle),0)));
b.target = self;
b.A_CheckTerrain();
}
vel.xy += (RotateVector(NormalizedMove(),angle)/6400.)*TweakSpeed();
}
}
int FastCheck()
{
if ( !player ) return 1;
if ( bWalking || (NormalizedMove().length() <= 8000.) ) return 0; // walk
if ( player.cmd.buttons&BT_RUN ) return 2; // sprint
return 1; // walk
}
State A_FastJump( StateLabel walk = null, StateLabel run = null, StateLabel sprint = null )
{
int fc = FastCheck();
if ( (fc == 2) && sprint ) return ResolveState(sprint);
if ( (fc == 1) && run ) return ResolveState(run);
if ( (fc == 0) && walk ) return ResolveState(walk);
return ResolveState(null);
}
States
{
Spawn:
// normal idle
#### # 2;
XZW1 A 1 A_JumpIf(player&&(player.mo==self)&&(abs(player.cmd.yaw|player.cmd.pitch)>128),"Turn");
Wait;
See:
// normal walking
#### # 2;
XZW1 BCD 2 A_FastJump(null,"SeeRun","SeeFast");
XZW1 E 0 A_Footstep(1);
XZW1 EFGHIJKL 2 A_FastJump(null,"SeeRun","SeeFast");
XZW1 M 0 A_Footstep(-1);
XZW1 MNOPA 2 A_FastJump(null,"SeeRun","SeeFast");
Goto See+1;
Turn:
#### # 8 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZW1 C 1 A_JumpIf(!player||!(player.cmd.yaw|player.cmd.pitch),1);
Wait;
XZW1 C 3 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
Goto Spawn+1;
SeeRun:
#### # 2;
XZWI PQR 2 A_FastJump("See",null,"SeeFast");
XZWI S 0 A_Footstep(1,1);
XZWI STUVWX 2 A_FastJump("See",null,"SeeFast");
XZWI Y 0 A_Footstep(-1,1);
XZWI YZ 2 A_FastJump("See",null,"SeeFast");
XZW1 A 2 A_FastJump("See",null,"SeeFast");
Goto SeeRun+1;
SeeFast:
// sprinting
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
XZW1 QRS 2;
Goto SeeFastLoop;
SeeFastLoop:
// keep sprinting
XZW1 T 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 U 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 V 0 A_Footstep(1,2);
XZW1 V 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 W 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 X 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 Y 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW1 Z 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 A 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 B 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 C 0 A_Footstep(-1,2);
XZW2 C 1 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 D 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 E 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 F 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
XZW2 G 2 A_JumpIf(FastCheck()!=2,"SeeFastEnd");
Goto SeeFastLoop;
SeeFastEnd:
// brake
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.3);
XZW2 HIJKL 2;
Goto Spawn+1;
Pain:
// ouchy
XZW1 A 1 A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchPain");
XZW2 M 1 A_DemoPain();
XZW2 NOPQ 1;
Goto Spawn+1;
Death:
XDeath:
// ded
XZW1 A 0 A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchDeath");
XZW1 A 2
{
A_DemoScream();
bSOLID = true; // we need this to avoid clipping into things
}
XZW2 RSTUVWXYZ 2;
XZW3 ABCDEFG 2;
XZW3 H 1 A_DMFade();
Wait;
Boost:
// start boost
#### # 2;
XZW3 IJKLMNO 2
{
if ( player.onground||bNoGravity )
return ResolveState("BoostEnd");
A_BoostUp(true);
return ResolveState(null);
}
// keep boost
XZW3 P 1
{
if ( player.onground||bNoGravity )
return ResolveState("BoostEnd");
A_BoostUp(false);
return ResolveState(null);
}
XZW3 P 1 A_JumpIf((vel.z<-10)&&(pos.z>(floorz+80)),"Fall");
Goto Boost+8;
BoostEnd:
// stop boost
#### # 2;
XZW3 PQRSTUVW 2;
Goto Spawn+1;
Jump:
#### # 2;
XZWJ ABCDEF 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
Goto FallLoop;
Fall:
// start fall
#### # 4;
XZW3 XYZ 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
XZW4 AB 2 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
Goto FallLoop;
FallLoop:
// falling
XZW4 CDEFGH 3 A_JumpIf(player.onground&&!bNoGravity,"FallEnd");
Goto FallLoop;
FallEnd:
// landing
XZW4 CIJKLMN 2;
Goto Spawn+1;
Dash:
#### # 2;
XZW4 O 2 A_Dash();
XZW4 PQRS 2 A_Dash();
Goto Dash+2;
DashEnd:
XZW4 TUVWX 2;
Goto Spawn+1;
Wave:
#### # 3;
XZW4 YZ 3;
XZW5 ABCDEFGHIJKLM 3;
Goto Spawn+1;
Approve:
#### # 3;
XZW5 NOPQR 3;
XZW5 S 3 { facegrin = true; }
XZW5 TUVWXYZ 3;
XZW6 ABCD 3;
Goto Spawn+1;
Victory:
#### # 3;
XZW6 EFGH 3;
XZW6 I 3 { facegrin = true; }
XZW6 JKLMNOPQRSTUVW 3;
Goto Spawn+1;
BlowKiss:
#### # 3;
XZWD EFGH 3;
XZWD I 3 { faceblink = true; }
XZWD JKLMNO 3;
XZWD P 3 { facewink = true; }
XZWD QRSTUVW 3;
Goto Spawn+1;
Headpat:
#### # 3;
XZWH ST 3;
XZWH U 2;
XZWH V 2 { facegrin = true; }
XZWH W 2;
XZWH XYZ 1;
XZWI A 1;
XZWI B 2;
XZWI C 2 { facegrin = true; }
XZWI DE 2;
XZWI FG 1;
HeadpatLoop:
XZWI H 1;
XZWH XYZ 1;
XZWI A 1;
XZWI B 2;
XZWI C 2 { facegrin = true; }
XZWI DE 2;
XZWI FGH 1;
XZWI IJK 2;
XZWI LMNO 3;
Goto Spawn+1;
Ragepat:
#### # 3;
XZWH ST 2;
XZWH UVW 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI FH 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI FH 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI F 1;
RagepatLoop:
XZWI H 1;
XZWH XZ 1;
XZWI BCDE 1;
XZWI FH 1;
XZWI IJK 1;
XZWI LMNO 2;
Goto Spawn+1;
Missile:
// attacking
#### # 2;
XZW6 XYZ 2;
XZW7 ABC 2;
Goto Spawn+1;
Melee:
// ponch
#### # 2;
XZW8 TUVWXYZ 2;
XZW9 ABCDEF 2;
Goto Spawn+1;
FastMelee:
// ponch (fast)
#### # 2;
XZW8 TUVWXYZ 1;
XZW9 ABCDE 1;
XZW9 F 2;
Goto Spawn+1;
Reload:
// reload
#### # 2;
XZW9 GHIJKLMNOPQRSTUVWXYZ 2;
XZWA ABCDE 2;
Goto Spawn+1;
FastReload:
// reload (fast)
#### # 2;
XZW9 GHIJKLMNOPQRSTUVWXYZ 1;
XZWA ABCDE 1;
Goto Spawn+1;
CheckGun:
// speen
#### # 2;
XZWA FGHIJKLMNOPQRSTUVWXY 2;
Goto Spawn+1;
StartCrouch:
// go crouching
#### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
XZW7 DEFGH 1;
XZW7 IJKL 2;
Goto Crouch+1;
Crouch:
#### # 4;
XZW7 M -1;
Stop;
CrouchMove:
#### # 4;
XZW7 MN 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZW7 OPQRS 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZW7 TUV 4 A_FastJump(null,"CrouchMoveRun","CrouchMoveFast");
Goto CrouchMove+1;
CrouchMoveRun:
#### # 3;
XZW7 MN 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZW7 OPQRS 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZW7 TUV 3 A_FastJump("CrouchMove",null,"CrouchMoveFast");
Goto CrouchMoveRun+1;
CrouchMoveFast:
#### # 2;
XZW7 MN 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZW7 OPQRS 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZW7 TUV 2 A_FastJump("CrouchMove","CrouchMoveRun",null);
Goto CrouchMoveFast+2;
CrouchWave:
#### # 3;
XZWF ABCDEFGHIJKLMNO 3;
Goto Crouch+1;
CrouchApprove:
#### # 3;
XZWF PQRST 3;
XZWF U 3 { facegrin = true; }
XZWF VWXYZ 3;
XZWG ABCDEF 3;
Goto Crouch+1;
CrouchVictory:
#### # 3;
XZWG GHIJ 3;
XZWG K 3 { facegrin = true; }
XZWG LMNOPQRSTUVWXY 3;
Goto Crouch+1;
CrouchBlowKiss:
#### # 3;
XZWG Z 3;
XZWH ABC 3;
XZWH D 3 { faceblink = true; }
XZWH EFGHIJ 3;
XZWH K 3 { facewink = true; }
XZWH LMNOPQR 3;
Goto Crouch+1;
CrouchMissile:
XZW7 M 2;
XZW7 WXYZ 2;
XZW8 AB 2;
Goto Crouch+1;
CrouchMelee:
XZW7 M 2;
XZWA Z 2;
XZWB ABCDEFGHIJKL 2;
Goto Crouch+1;
CrouchFastMelee:
XZW7 M 2;
XZWA Z 1;
XZWB ABCDEFGHIJK 1;
XZWB L 2;
Goto Crouch+1;
CrouchReload:
XZW7 M 2;
XZWB MNOPQRSTUVWXYZ 2;
XZWC ABCDEFGHIJ 2;
Goto Crouch+1;
CrouchFastReload:
XZW7 M 2;
XZWB MNOPQRSTUVWXYZ 1;
XZWC ABCDEFGHIJ 1;
Goto Crouch+1;
CrouchCheckGun:
XZW7 M 2;
XZWC LMNOPQRSTUVWXYZ 2;
XZWD ABCD 2;
Goto Crouch+1;
CrouchPain:
XZW7 M 1;
XZW8 C 1 A_DemoPain();
XZW8 DEF 1;
Goto Crouch+1;
CrouchDeath:
XZW7 M 2
{
A_DemoScream();
bSOLID = true; // we need this to avoid clipping into things
}
XZW8 GHIJK 2;
XZW8 L 1 A_DMFade();
Wait;
EndCrouch:
#### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45);
XZW8 MNOPQRS 2;
Goto Spawn+1;
Float:
#### # 3;
XZWD XYZ 3;
XZWE ABCDEFGHI 3;
Goto Float+1;
Swim:
#### # 2;
XZWE JKL 2;
Goto SwimLoop;
SwimLoop:
#### # 5;
XZWE MNO 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZWE PQRSTU 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.1);
XZWE VWX 5 A_FastJump(null,"SwimLoopRun","SwimLoopFast");
Goto SwimLoop+1;
SwimLoopRun:
#### # 3;
XZWE MNO 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZWE PQRSTU 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2);
XZWE VWX 3 A_FastJump("SwimLoop",null,"SwimLoopFast");
Goto SwimLoopRun+1;
SwimLoopFast:
#### # 2;
XZWE MNO 2 A_FastJump("SwimLoop","SwimLoopRun",null);
XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZWE PQRSTU 2 A_FastJump("SwimLoop","SwimLoopRun",null);
XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.4);
XZWE VWX 2 A_FastJump("SwimLoop","SwimLoopRun",null);
Goto SwimLoopFast+1;
SwimEnd:
#### # 2;
XZWE MYZ 2;
Goto Float+1;
}
}

View file

@ -0,0 +1,359 @@
// the give/take cheat, oh boy
extend Class Demolitionist
{
void CheatGive_Health( int amount = 0 )
{
player.health = health = (amount>0)?(health+amount):default.health;
}
void CheatGive_Backpack()
{
let def = GetDefaultByType('HammerspaceEmbiggener');
GiveInventory('TradedHammerspaceEmbiggener',def.MaxAmount,true);
}
void CheatGive_Ammo()
{
// Max out all mod ammo
foreach ( cls:AllActorClasses )
{
let type = (class<Ammo>)(cls);
if ( !type || type.IsAbstract() || (type.GetParentClass() != 'SWWMAmmo') ) continue;
let ammoitem = Ammo(FindInventory(type));
if ( !ammoitem )
{
// Add it first if not found
ammoitem = Ammo(Spawn(type));
ammoitem.Amount = 0;
ammoitem.AttachToOwner(self);
}
// Don't give spares unless we own a Candy Gun, for consistency
if ( (type is 'CandyGunSpares') && !FindInventory('CandyGun') ) continue;
// Top up
ammoitem.Amount = ammoitem.MaxAmount;
// Does it have mag ammo?
let sammoitem = SWWMAmmo(ammoitem);
if ( !sammoitem || !sammoitem.MagAmmoType ) continue;
let magitem = MagAmmo(FindInventory(sammoitem.MagAmmoType));
if ( !magitem )
{
// Add it first if not found (shouldn't happen)
magitem = MagAmmo(Spawn(sammoitem.MagAmmoType));
magitem.AttachToOwner(self);
}
// Top up
magitem.Amount = magitem.MaxAmount;
}
}
void CheatGive_Armor()
{
// only give armors that have spares associated
foreach ( cls:AllActorClasses )
{
let type = (Class<SWWMSpareArmor>)(cls);
if ( !type || type.IsAbstract() || (type == 'SWWMSpareArmor') ) continue;
if ( GetReplacement(type) != type ) continue;
let def = GetDefaultByType(type);
let armo = SWWMArmor(FindInventory(def.giveme));
if ( !armo )
{
armo = SWWMArmor(Spawn(def.giveme));
armo.AttachToOwner(self);
}
armo.Amount = armo.MaxAmount;
}
}
void CheatGive_Keys()
{
foreach ( cls:AllActorClasses )
{
let type = (Class<Key>)(cls);
if ( !type ) continue;
let keyitem = GetDefaultByType(type);
if ( !keyitem.special1 ) continue;
let rep = GetReplacement(type); // handle replaced keys
if ( !(rep is 'Key') ) continue;
// iwad restrictions (vanilla doesn't care, but here they'll show in the inventory)
if ( !(gameinfo.gametype&GAME_HERETIC) && ((rep is 'SWWMKeyGreen') || (rep is 'SWWMKeyBlue') || (rep is 'SWWMKeyYellow')) )
continue;
if ( !(gameinfo.gametype&GAME_DOOM) && ((rep is 'SWWMRedCard') || (rep is 'SWWMBlueCard') || (rep is 'SWWMYellowCard')) )
continue;
let item = Inventory(Spawn(rep));
SWWMHandler.KeyTagFix(item);
if ( item is 'SWWMKey' ) SWWMKey(item).propagated = true; // no anim
key_reentrant = true;
if ( !item.CallTryPickup(self) ) item.Destroy();
key_reentrant = false;
}
}
void CheatGive_Weapons()
{
let savedpending = player.PendingWeapon;
foreach ( cls:AllActorClasses )
{
let type = (class<Weapon>)(cls);
if ( !type || (type == "Weapon") ) continue;
// Don't give already owned weapons
let owned = FindInventory(type);
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
// Don't give replaced weapons unless the replacement was done by Dehacked.
let rep = GetReplacement(type);
if ( (rep == type) || (rep is "DehackedPickup") )
{
// Give the weapon only if it is set in a weapon slot.
if ( !player.weapons.LocateWeapon(type) ) continue;
readonly<Weapon> def = GetDefaultByType(type);
if ( !def.bCheatNotWeapon && def.CanPickup(self) )
GiveInventory(type,1,true);
}
}
player.PendingWeapon = savedpending;
}
void CheatGive_Artifacts( int amount = 0 )
{
foreach ( cls:AllActorClasses )
{
let type = (class<Inventory>)(cls);
if ( !type ) continue;
let rep = GetReplacement(type);
// don't give replaced items
if ( rep != type ) continue;
// no fabricators before hexen
if ( !(gameinfo.gametype&GAME_HEXEN) && (type is 'AmmoFabricator') ) continue;
// no barriers outside doom
if ( !(gameinfo.gametype&GAME_DOOM) && (type is 'EBarrier') ) continue;
// no gravity/tether outside raven
if ( !(gameinfo.gametype&GAME_RAVEN) && ((type is 'GravitySuppressor') || (type is 'SafetyTether')) ) continue;
// Don't give maxed items
let owned = FindInventory(type);
if ( owned && (owned.Amount >= owned.MaxAmount) ) continue;
let def = GetDefaultByType(type);
// must have INVBAR and a valid icon (also can't be a puzzle item)
if ( !def.bINVBAR || !def.ShouldSpawn() || !def.Icon.isValid() || (type is 'PuzzleItem') ) continue;
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
}
void CheatGive_PuzzlePieces( int amount = 0 )
{
foreach ( cls:AllActorClasses )
{
let type = (class<PuzzleItem>)(cls);
if ( !type ) continue;
let def = GetDefaultByType(type);
if ( !def.Icon.isValid() ) continue;
GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
}
}
void CheatGive_Collectibles()
{
foreach ( cls:AllActorClasses )
{
let type = (class<SWWMCollectible>)(cls);
if ( !type || (type == 'SWWMCollectible') ) continue;
let def = GetDefaultByType(type);
// check that we can collect it in this IWAD
if ( !def.ValidGame() ) continue;
let item = Inventory(Spawn(cls));
SWWMCollectible(item).propagated = true; // no score or anims
if ( !item.CallTryPickup(self) ) item.Destroy();
}
}
override void CheatGive( String name, int amount )
{
if ( !player.mo || (player.health <= 0) ) return;
ingivecheat = true;
int giveall = (name~=="everything")?ALL_YESYES:(name~=="all")?ALL_YES:ALL_NO;
if ( giveall )
{
CheatGive_Health();
CheatGive_Backpack();
CheatGive_Armor();
CheatGive_Keys();
CheatGive_Weapons();
CheatGive_Ammo();
CheatGive_Artifacts();
CheatGive_PuzzlePieces();
if ( giveall == ALL_YESYES )
CheatGive_Collectibles();
}
else if ( name ~== "health" ) CheatGive_Health(amount);
else if ( name ~== "backpack" ) CheatGive_Backpack();
else if ( name ~== "ammo" ) CheatGive_Ammo();
else if ( name ~== "armor" ) CheatGive_Armor();
else if ( name ~== "keys" ) CheatGive_Keys();
else if ( name ~== "weapons" ) CheatGive_Weapons();
else if ( name ~== "artifacts" ) CheatGive_Artifacts(amount);
else if ( name ~== "puzzlepieces" ) CheatGive_PuzzlePieces(amount);
else if ( name ~== "collectibles" ) CheatGive_Collectibles();
else
{
Class<Inventory> type = name;
if ( !type || type.IsAbstract() )
{
if ( CheckLocalView() )
Console.Printf("'%s' is not a valid inventory item",name);
ingivecheat = false;
return;
}
GiveInventory(type,amount,true);
}
ingivecheat = false;
}
void CheatTake_Health( int amount = 0 )
{
if ( amount >= health )
{
CheatSuicide();
if ( player == players[consoleplayer] ) Console.HideConsole();
return;
}
player.health = health -= amount;
}
void CheatTake_Backpack()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
if ( (i is 'HammerspaceEmbiggener') || (i is 'BackpackItem') )
i.DepleteOrDestroy();
i = next;
}
}
void CheatTake_Ammo()
{
for ( Inventory i=inv; i; i=i.inv )
{
if ( (i is 'Ammo') || (i is 'MagAmmo') )
i.Amount = 0;
}
}
void CheatTake_Armor()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
// avoid unclearable (internal) or nodrain (powerup) armors
if ( ((i is 'SWWMArmor') && !i.bUNCLEARABLE && !(SWWMArmor(i).bNODRAIN)) || (i is 'SWWMSpareArmor') )
i.DepleteOrDestroy();
i = next;
}
}
void CheatTake_Keys()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
if ( i is 'Key' ) i.DepleteOrDestroy();
i = next;
}
}
void CheatTake_Weapons()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
// don't take away gestures
if ( (i is 'Weapon') && !(i is 'SWWMGesture') && !(i is 'SWWMItemGesture') )
i.DepleteOrDestroy();
i = next;
}
}
void CheatTake_Artifacts()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
if ( i.bINVBAR && i.Icon.isValid() && !(i is 'PuzzleItem') )
i.DepleteOrDestroy();
i = next;
}
}
void CheatTake_PuzzlePieces()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
if ( i is 'PuzzleItem' ) i.DepleteOrDestroy();
i = next;
}
}
void CheatTake_Collectibles()
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
if ( i is 'SWWMCollectible' ) i.DepleteOrDestroy();
i = next;
}
if ( mystats ) mystats.ownedcollectibles.Clear();
}
void CheatTake_Item( Class<Inventory> type, int amount = 0 )
{
Inventory i = inv;
while ( i )
{
let next = i.inv;
if ( i is type )
{
i.Amount -= max(amount,1);
if ( i.Amount <= 0 )
{
if ( (i is 'SWWMCollectible') && mystats )
{
// remove from obtained list
let idx = mystats.ownedcollectibles.Find(SWWMCollectible(i).GetClass());
if ( idx < mystats.ownedcollectibles.Size() )
mystats.ownedcollectibles.Delete(idx);
}
i.DepleteOrDestroy();
}
}
i = next;
}
}
override void CheatTake( String name, int amount )
{
if ( !player.mo || (player.health <= 0) ) return;
int takeall = (name~=="everything")?ALL_YESYES:(name~=="all")?ALL_YES:ALL_NO;
if ( takeall )
{
CheatTake_Ammo();
CheatTake_Backpack();
CheatTake_Armor();
CheatTake_Keys();
CheatTake_Weapons();
CheatTake_Artifacts();
CheatTake_PuzzlePieces();
if ( takeall == ALL_YESYES )
CheatTake_Collectibles();
}
else if ( name ~== "health" ) CheatTake_Health(amount);
else if ( name ~== "backpack" ) CheatTake_Backpack();
else if ( name ~== "ammo" ) CheatTake_Ammo();
else if ( name ~== "armor" ) CheatTake_Armor();
else if ( name ~== "keys" ) CheatTake_Keys();
else if ( name ~== "weapons" ) CheatTake_Weapons();
else if ( name ~== "artifacts" ) CheatTake_Artifacts();
else if ( name ~== "puzzlepieces" ) CheatTake_PuzzlePieces();
else if ( name ~== "collectibles" ) CheatTake_Collectibles();
else
{
Class<Inventory> type = name;
if ( !type || type.IsAbstract() )
{
if ( CheckLocalView() )
Console.Printf("'%s' is not a valid inventory item",name);
return;
}
CheatTake_Item(type,amount);
}
}
}

View file

@ -0,0 +1,82 @@
// unique voodoo doll, for better compatibility
Class SWWMVoodooDoll : PlayerPawn
{
Vector3 lastvel;
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
// as simple as it gets
return "$OB_VOODOO";
}
override void Tick()
{
Super.Tick();
// mikoportal compat
if ( !(pos.z ~== -32768) ) return;
if ( vel.length() <= lastvel.length() )
vel = lastvel;
lastvel = vel;
}
Default
{
Tag "$FN_VOODOO";
Speed 1;
Health 100;
Radius 16;
Height 56;
Mass 100;
PainChance 255;
+NOBLOOD;
+DONTGIB;
+NOICEDEATH;
+DONTCORPSE;
+NOSKIN;
-FRIENDLY;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Pain:
XZW1 A 1;
XZW1 B 2 A_StartSound("voodoodoll/hit",CHAN_BODY,CHANF_OVERLAP);
XZW1 CDEF 2;
Goto Spawn;
Death:
XZW1 A 2
{
A_StartSound("voodoodoll/fall",CHAN_BODY,CHANF_OVERLAP);
A_NoBlocking();
}
XZW1 GHIJKLMNO 2;
XZW1 PQR 2;
XZW1 S -1;
Stop;
}
}
// for the doom 2 cast
Class CastDemolitionist : Actor
{
Default
{
DeathSound "demolitionist/death";
}
States
{
Spawn:
See:
ZYX1 ABCDEFGHIJKLMNOP 2;
Loop;
Missile:
ZYX1 A 2;
ZYX2 ABCDEF 2;
Goto See;
Death:
ZYX1 A 2;
ZYX3 ABCDEFGHIJKLMNOP 2;
ZYX3 Q -1;
Stop;
}
}

View file

@ -0,0 +1,161 @@
// facial animation and cosmetics
extend Class Demolitionist
{
private int GetRandom()
{
return (rss = (rss<<1)*35447+(rss/87));
}
private void UpdateTags()
{
if ( !tagcolor ) tagcolor = CVar.GetCVar('swwm_tagcolor',player);
static const String colname[] =
{
"",
"Blue",
"Cyan",
"Dragonfly",
"Gold",
"Magenta",
"Orange",
"Peach",
"Pink",
"Purple",
"Red",
"Violet",
"White",
"Yellow",
"Black",
"Rust"
};
int idx = tagcolor.GetInt();
if ( (idx < 0) || (idx >= colname.Size()) ) idx = 0;
if ( idx != oldtagcolor )
A_ChangeModel("",0,"","",0,"models","DemoTags"..colname[idx]..".png",CMDL_USESURFACESKIN,-1);
oldtagcolor = idx;
for ( Inventory i=inv; i; i=i.inv )
{
if ( !(i is 'SWWMWeapon') ) continue;
SWWMWeapon(i).UpdateTags(idx,colname[idx]);
}
}
private void UpdateFace()
{
// damage handling
if ( facedamage )
{
if ( lastdamage > 70 )
{
facestate = FS_OUCH;
facetimer = (lastdamagetimer-gametic)+10;
}
else if ( facestate < FS_OUCH )
{
facestate = FS_PAIN;
facetimer = (lastdamagetimer-gametic)+10;
paindir = 0;
// paraphrased from vanilla, with some tweaks
if ( player.attacker && (player.attacker != self) )
{
double atkang = AngleTo(player.attacker);
double angdiff = deltaangle(angle,atkang);
if ( abs(angdiff) < 135 )
{
if ( angdiff > 45 ) paindir = -1;
else if ( angdiff < -45 ) paindir = 1;
}
}
}
}
facedamage = false;
if ( facegrin && (facestate < FS_SAD) )
{
facestate = FS_GRIN;
facetimer = 50;
}
facegrin = false;
if ( facewink && (facestate < FS_SAD) )
{
facestate = FS_WINK;
facetimer = 20;
}
facewink = false;
if ( faceblink && (facestate < FS_PAIN) )
{
facestate = FS_BLINK;
facetimer = 30;
}
faceblink = false;
if ( facesad && (facestate <= FS_SAD) )
{
facestate = FS_SAD;
facetimer = 50;
}
facesad = false;
if ( FindInventory("RagekitPower") && (facestate < FS_PAIN) )
{
facestate = FS_EVIL;
facetimer = 10;
}
if ( facetimer > 0 )
{
facetimer--;
if ( facetimer <= 0 )
{
facestate = FS_DEFAULT;
blinktime = 30;
}
}
if ( !(gametic&1) )
{
if ( blinktime <= 0 )
{
blinktime--;
if ( blinktime < -3 )
{
rss = MSTime();
blinktime = (abs(GetRandom())%10)?(40+abs(GetRandom())%40):6;
}
}
else blinktime--;
}
// update our face texture if different
static const String facetex[] =
{
"Blank", "Blink", "Booty", "Dead",
"Default", "Dizzy", "Evil", "Grin",
"Hurt", "HurtLeft", "HurtRight",
"Off", "Ouch", "Sad", "Smug",
"Unamused", "Wink"
};
int faceidx = GetFaceTex();
if ( !oldfaceidx || (faceidx != oldfaceidx) )
A_ChangeModel("",0,"","",1,"models","DemoFace_"..facetex[faceidx]..".png",CMDL_USESURFACESKIN,-1);
oldfaceidx = faceidx;
}
private int GetFaceTex()
{
if ( player.Health <= 0 ) return 3;
if ( (bInvulnerable || (player.cheats&(CF_GODMODE|CF_GODMODE2)) || FindInventory("InvinciballPower")) && (facestate >= FS_PAIN) ) return 14;
if ( facestate == FS_OUCH ) return 12;
if ( facestate == FS_PAIN ) return (paindir==1)?10:(paindir==-1)?9:8;
if ( facestate == FS_GRIN ) return 7;
if ( facestate == FS_EVIL ) return 6;
if ( facestate == FS_SAD ) return 13;
if ( facestate == FS_WINK ) return 16;
if ( facestate == FS_BLINK ) return ((facetimer>28)||(facetimer<2))?15:1;
switch ( blinktime )
{
case -1:
case -3:
return 15;
break;
case -2:
return 1;
break;
}
return 4;
}
}

View file

@ -0,0 +1,211 @@
// inventory functions
extend Class Demolitionist
{
override void GiveDefaultInventory()
{
if ( !player ) return;
bInDefaultInventory = true; // prevent default inventory from adding lore entries (fixes lore library disappearing when loading saves WHILE DEAD)
// we don't need hexen or basic armors
// we can simplify the code a lot here since no handling of many special conditions is needed
let sc = Inventory(Spawn('SayaCollar'));
if ( !sc.CallTryPickup(self) ) sc.Destroy();
let ap = Inventory(Spawn('AlmasteelPlating'));
if ( !ap.CallTryPickup(self) ) ap.Destroy();
let dp = Inventory(Spawn('DeepImpact'));
if ( !dp.CallTryPickup(self) ) dp.Destroy();
let eg = ExplodiumGun(Spawn('ExplodiumGun'));
eg.firstselect = true;
if ( !eg.CallTryPickup(self) ) eg.Destroy();
else player.ReadyWeapon = player.PendingWeapon = eg;
// in hexdd, we start with the chaos sphere in our grasp
if ( SWWMUtility.IsDeathkings() && SWWMUtility.CheckMD5List("vanillahexen.list") )
{
let cs = Inventory(Spawn('SWWMChaosSphere'));
if ( !cs.CallTryPickup(self) ) cs.Destroy();
}
// in deathmatch, we start with 8 embiggeners
if ( deathmatch )
{
let em = Inventory(Spawn('TradedHammerspaceEmbiggener'));
em.Amount = em.MaxAmount;
if ( !em.CallTryPickup(self) ) em.Destroy();
}
bInDefaultInventory = false;
}
override void ClearInventory()
{
Super.ClearInventory();
// remove some specific "undroppable" items
Actor last = self;
while ( last.inv )
{
let i = last.inv;
if ( ((i is 'SWWMArmor') && !i.bUNCLEARABLE) || (i is 'HammerspaceEmbiggener') )
{
i.DepleteOrDestroy();
if ( !i.bDestroyed ) last = i;
}
else last = i;
}
}
override void RemoveInventory( Inventory item )
{
// stop any looping sounds our current weapon had
if ( (item is 'SWWMWeapon') && player && (SWWMWeapon(item) == player.ReadyWeapon) )
{
A_StopSound(CHAN_WEAPONEXTRA);
A_StopSound(CHAN_WEAPONEXTRA2);
A_StopSound(CHAN_WEAPONEXTRA3);
}
Super.RemoveInventory(item);
}
override void AddInventory( Inventory item )
{
Super.AddInventory(item);
if ( !player ) return;
String cn = item.GetClassName();
if ( !bInDefaultInventory && (level.maptime > 0) && (!(item is 'SWWMWeapon') || !(cn.Left(4) ~== "Dual")) ) // dual weapons are ignored here, as they're handled separately
{
// add lore if any
SWWMLoreLibrary.Add(player,cn);
// weapon get oneliner
if ( (item is 'Weapon') && !(item is 'SWWMGesture') && !(item is 'SWWMItemGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && (player == players[consoleplayer]) && !ingivecheat )
{
if ( (item is 'HeavyMahSheenGun') && !Random[DemoLines](0,2) && SWWMHandler.AddOneliner("sheenspecial",2,20) )
{
A_StartSound("sheen/specialpick",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
A_StartSound("sheen/specialpick",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
}
else if ( (item is 'SWWMWeapon') && (SWWMWeapon(item).GetLine != "") )
{
// fall back to generic weapon get if voicepack lacks weapon-specific lines
if ( !SWWMHandler.AddOneliner(SWWMWeapon(item).GetLine,2) )
SWWMHandler.AddOneliner("getweapon",2);
}
else SWWMHandler.AddOneliner("getweapon",2);
facegrin = true;
}
}
if ( (item is 'Key') && !key_reentrant && !deathmatch && !bInDefaultInventory )
{
// score
int score = 100;
if ( player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),item.GetTag(),score);
else Console.Printf(StringTable.Localize("$SWWM_FINDKEYREM"),player.GetUserName(),item.GetTag(),score);
SWWMCredits.Give(player,score);
SWWMScoreObj.Spawn(100,player.mo.Vec3Offset(0,0,Height/2));
if ( !ingivecheat )
{
if ( !Random[DemoLines](0,5) &&
((item.GetClass() == 'RedSkull') || (item.GetClass() == 'YellowSkull')
|| (item.GetClass() == 'BlueSkull') || (item.GetClassName() == "PurpleSkull")) )
{
if ( !SWWMHandler.AddOneliner("skullget",2) )
SWWMHandler.AddOneliner("keyget",2);
}
else SWWMHandler.AddOneliner("keyget",2);
facegrin = true;
}
// share all keys in mp
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo || (i == PlayerNumber()) )
continue;
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = true;
let tkey = Inventory(Spawn(item.GetClass()));
SWWMHandler.KeyTagFix(tkey);
if ( !tkey.CallTryPickup(players[i].mo) ) tkey.Destroy();
if ( players[i].mo is 'Demolitionist' ) Demolitionist(players[i].mo).key_reentrant = false;
}
}
// add collectible to stats
if ( item is 'SWWMCollectible' )
{
if ( !ingivecheat )
{
SWWMHandler.AddOneliner(SWWMCollectible(item).GetLine,2);
facegrin = true;
}
if ( !mystats ) return;
let cls = item.GetClass();
if ( (mystats.ownedcollectibles.Size() > 0) && (mystats.ownedcollectibles.Find(cls) < mystats.ownedcollectibles.Size()) ) return;
mystats.ownedcollectibles.Push(cls);
}
// yorick
if ( (item is 'PuzzSkull') && mystats && !mystats.gotyorick && !ingivecheat )
{
mystats.gotyorick = true;
SWWMHandler.AddOneliner("skullget",2);
facegrin = true;
}
// notify key obtained to flash icon
if ( item is 'Key' )
EventHandler.SendInterfaceEvent(consoleplayer,"swwmkeyget."..item.GetClassName(),PlayerNumber());
}
override bool UseInventory( Inventory item )
{
let itemtype = item.GetClass();
if ( (player.cheats&CF_TOTALLYFROZEN) || isFrozen() ) return false;
if ( !Actor.UseInventory(item) )
{
if ( player == players[consoleplayer] )
{
if ( !(item is 'Weapon') && !(item is 'SWWMCollectible') && !(item is 'Key') )
A_StartSound("menu/noinvuse",CHAN_ITEMEXTRA);
if ( item is 'PuzzleItem' )
SWWMHandler.AddOneliner("puzzfail",2,20);
}
return false;
}
// use sounds of big powerups are heard by other players
if ( (player == players[consoleplayer]) || item.bBIGPOWERUP )
A_StartSound(item.UseSound,CHAN_ITEMEXTRA);
if ( (player == players[consoleplayer]) && (item is 'PuzzleItem') )
SWWMHandler.AddOneliner("puzzsucc",2,10);
return true;
}
// the default PickWeapon code breaks when dual weapons are involved
// so we gotta roll out our own here
//
// I could rewrite PickNextWeapon and PickPrevWeapon as well, but nah
// those always call CheckAmmo so it's fine
private Weapon TraverseSlot( int slot, bool checkammo, int start, int end, Weapon cur )
{
for ( int i=start; i>=end; i-- )
{
let type = player.weapons.GetWeapon(slot,i);
let w = Weapon(player.mo.FindInventory(type));
if ( !w || (w == cur) || (cur && cur.bPOWERED_UP && (w == cur.SisterWeapon)) )
continue;
String sn = w.GetClassName();
bool dual = ((type is 'SWWMWeapon') && (sn.Left(4) ~== "Dual"));
if ( (!checkammo && (!dual || (w.SisterWeapon && (w.SisterWeapon.Amount > 1))))
|| w.CheckAmmo(Weapon.EitherFire,false) )
return w;
}
return null;
}
override Weapon PickWeapon( int slot, bool checkammo )
{
int slotsize = player.weapons.SlotSize(slot);
let cur = player.ReadyWeapon;
bool found = false;
int cs, ci;
if ( cur ) [found, cs, ci] = player.weapons.LocateWeapon(cur.GetClass());
if ( found && (cs == slot) )
{
// traverse the slot down from current index
let w = TraverseSlot(slot,checkammo,ci-1,0,cur);
if ( !w ) w = TraverseSlot(slot,checkammo,slotsize-1,ci+1,cur);
return w?w:cur;
}
let w = TraverseSlot(slot,checkammo,slotsize-1,0,cur);
return w?w:cur;
}
}

View file

@ -0,0 +1,640 @@
// general movement code
extend Class Demolitionist
{
// directional movement without straferunning
Vector2 NormalizedMove()
{
if ( !(player.cmd.forwardmove|player.cmd.sidemove) )
return (0,0);
int idx = !!(player.cmd.buttons&BT_RUN);
// ratio between forwardmove and sidemove (depending on BT_RUN state)
double fs = gameinfo.normforwardmove[idx]/gameinfo.normsidemove[idx];
// raw axes scaled to 1:1 ratio
Vector2 mvec = (player.cmd.forwardmove,-player.cmd.sidemove*fs);
// multiply unit vector back to "raw" running speed (as TweakSpeed handles the "true" scaling for us later)
return mvec.unit()*gameinfo.normforwardmove[1]*256.;
}
double TweakSpeed()
{
double fact = bWalking?.08:(player.cmd.buttons&BT_RUN)?1.1:.4;
for ( Inventory i=Inv; i; i=i.Inv ) fact *= i.GetSpeedFactor();
return fact;
}
override void CalcHeight()
{
double defviewh = viewheight+player.crouchviewdelta;
oldbob = player.bob;
if ( bFlyCheat || (player.cheats&CF_NOCLIP2) )
player.bob = 0.;
else
player.bob = min((player.vel dot player.vel)*player.GetMoveBob(),MAXBOB);
if ( player.cheats&CF_NOVELOCITY )
{
player.viewz = pos.z+defviewh;
if ( player.viewz > ceilingz-4 )
player.viewz = ceilingz-4;
SetViewPos((0.,0.,0.));
return;
}
// adjust viewheight
if ( player.playerstate == PST_LIVE )
{
player.viewheight += player.deltaviewheight;
if ( player.viewheight > defviewh )
{
player.viewheight = defviewh;
player.deltaviewheight = 0.;
}
else if ( player.viewheight < (defviewh/2.) )
{
player.viewheight = defviewh/2.;
if ( player.deltaviewheight <= 0. )
player.deltaviewheight = 1./65536.;
}
if ( player.deltaviewheight )
{
player.deltaviewheight += .25;
if ( !player.deltaviewheight )
player.deltaviewheight = 1./65536.;
}
}
// apply bobbing (formula adapted from Unreal)
oldbobtime = bobtime;
double vel2d = vel.xy.length();
if ( vel2d < .25 ) bobtime += .2/GameTicRate;
else bobtime += (.5+.8*min(vel2d/10.,3.))/GameTicRate;
Vector2 bob = (sin(bobtime*180.),sin(bobtime*360.))*player.bob*.25;
if ( player.morphtics )
bob = (0.,0.);
// set up viewz
player.viewz = pos.z+player.viewheight+(bob.y*clamp(viewbob,0.,1.5));
// handle smooth step down (hacky but looks ok)
player.viewz += ssup;
ssup = max(0,(ssup*.7)-.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;
// add viewpos Y for side bob
SetViewPos((0.,bob.x*clamp(viewbob,0.,1.5),0.));
}
override void CheckPitch()
{
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
return; // handled in moveplayer
Super.CheckPitch();
}
override void CheckMoveUpDown()
{
if ( InStateSequence(CurState,FindState("Dash")) )
player.cmd.upmove = 0;
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
double fs = TweakSpeed();
Vector3 x, y, z;
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
Vector3 accel;
if ( (player.cmd.upmove == -32768) || sendtoground )
{
sendtoground = true;
player.centering = true;
accel = (0,0,-4096);
}
else accel = z*player.cmd.upmove*8.;
accel *= fs/128.;
vel = vel+accel/GameTicRate;
if ( sendtoground ) vel.xy *= .6;
if ( (pos.z <= floorz) || bOnMobj ) sendtoground = false;
if ( vel.length() > 50. )
vel = vel.unit()*50.;
return;
}
else sendtoground = false;
Super.CheckMoveUpDown();
}
private bool ShouldDecelerate( Sector s )
{
// check if we can apply fast decel while standing on this sector
// (important to not break certain intended vanilla effects)
if ( bNOFRICTION )
return false; // we don't have friction at all (e.g.: while dashing)
if ( bWINDTHRUST && (s.special >= 40) && (s.special <= 51) )
return false; // wind
if ( (s.special == 84) || (s.special == 118) || (s.special >= 201) && (s.special <= 244) )
return false; // current
return true;
}
override void MovePlayer()
{
if ( !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) )
{
dashboost = 0.;
player.vel *= 0.;
if ( IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
Super.MovePlayer();
return;
}
bool isdashing = InStateSequence(CurState,FindState("Dash"));
if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0;
if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) )
{
player.cheats &= ~CF_SCALEDNOLERP; // we cannot permit this flag here
player.onground = false;
if ( player.turnticks )
{
player.turnticks--;
angle += (180./TURN180_TICKS);
}
else guideangle += .2*player.cmd.yaw*(360./65536.);
guidepitch -= .2*player.cmd.pitch*(360./65536.);
if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.);
guideroll = clamp(deltaangle(roll,0),-3.,3.);
A_SetAngle(angle+guideangle,SPF_INTERPOLATE);
A_SetPitch(clamp(pitch+guidepitch,player.MinPitch,player.MaxPitch),SPF_INTERPOLATE);
A_SetRoll(roll+guideroll,SPF_INTERPOLATE);
if ( (abs(roll) <= 1./65536.) && (abs(pitch) <= 1./65536.) )
{
guideroll = guidepitch = roll = pitch = 0.;
player.centering = false;
}
double fs = TweakSpeed();
double jcmove = 0.;
if ( player.cmd.buttons&BT_JUMP ) jcmove += 4096.;
if ( player.cmd.buttons&BT_CROUCH ) jcmove -= 4096.;
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector3 x, y, z;
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
Vector2 nmove = NormalizedMove();
Vector3 accel = x*nmove.x-y*nmove.y+z*jcmove;
accel *= fs/320.;
double spd = vel.length();
if ( spd > 40. ) vel = (vel+accel/GameTicRate).unit()*spd;
else vel = vel+accel/GameTicRate;
vel *= .95;
player.vel = (1,1)*vel.length();
player.jumptics = -2;
if ( !(player.cheats & CF_PREDICTING) && (player.cmd.forwardmove|player.cmd.sidemove) )
PlayRunning();
if ( player.cheats&CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
else
{
player.cheats |= CF_SCALEDNOLERP; // smoother turning
if ( player.turnticks )
{
player.turnticks--;
angle += (180./TURN180_TICKS);
}
else angle += player.cmd.yaw*(360./65536.);
player.onground = (pos.z<=floorz)||bOnMobj||bMBFBouncer||(player.cheats&CF_NOCLIP2);
if ( player.cmd.forwardmove|player.cmd.sidemove )
{
double bobfactor;
double friction, movefactor;
[friction, movefactor] = GetFriction();
bobfactor = (friction<ORIG_FRICTION)?movefactor:ORIG_FRICTION_FACTOR;
if ( !player.onground && !bNoGravity )
{
// no air control here, done afterwards
movefactor *= 0.;
bobfactor *= 0.;
}
// use normalized movement vector, no SR40 (not that we need it with how fast we can run)
Vector2 nmove = NormalizedMove();
nmove *= TweakSpeed();
nmove *= Speed/256.;
// When crouching, speed and bobbing have to be reduced
if ( CanCrouch() && (player.crouchfactor != 1) )
{
nmove *= player.crouchfactor;
bobfactor *= player.crouchfactor;
}
nmove *= movefactor;
if ( bNOGRAVITY && !player.GetClassicFlight() )
{
double zpush = nmove.x*sin(pitch);
vel.z -= zpush;
vel.xy += RotateVector(nmove,angle)*cos(pitch);
player.vel += RotateVector(nmove,angle)*bobfactor*cos(pitch)*16.;
}
else
{
vel.xy += RotateVector(nmove,angle);
if ( player.onground ) player.vel += RotateVector(nmove,angle)*bobfactor*16.;
else player.vel *= .75;
}
// override air control because we REALLY need the extra mobility
if ( !player.onground && !bNOGRAVITY )
{
nmove = NormalizedMove();
double fs = TweakSpeed();
if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor;
Vector2 accel = RotateVector(nmove,angle);
accel *= fs/320.;
double spd = vel.xy.length();
double maxspd = fs*12.;
if ( spd > maxspd ) vel.xy = (vel.xy+accel/GameTicRate).unit()*spd;
else vel.xy = vel.xy+accel/GameTicRate;
}
if ( !(player.cheats&CF_PREDICTING) && (nmove.length() > 0.) )
PlayRunning();
if ( player.cheats&CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
}
else if ( player.onground && ShouldDecelerate(floorsector) )
{
// quickly decelerate if we're not holding movement keys
// (account for slippery floors, and assume approx .9 friction as "midpoint" for 85% reduction)
double fact = clamp(floorsector.GetFriction(0)*.95,.5,1.);
vel *= fact;
player.vel *= fact;
}
if ( abs(roll) > 0. ) A_SetRoll(roll+clamp(deltaangle(roll,0),-3.,3.),SPF_INTERPOLATE);
}
guideangle *= .9;
guidepitch *= .9;
guideroll *= .9;
// anchor to ground when going down steps
if ( lastground && !player.onground && !bFly && !bFlyCheat && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z <= 0) && !isdashing )
{
// test for gap crossing (i.e: climbing up platforms with holes between them)
Vector3 storepos = pos;
double storefloorz = floorz;
bool crossgap = false;
for ( int i=1; i<=4; i++ ) // test up to 4 steps ahead, should be enough for most cases
{
SetOrigin(Vec3Offset(vel.x,vel.y,vel.z),true);
if ( floorz < storepos.z ) continue;
crossgap = true;
break;
}
SetOrigin(storepos,true);
floorz = storefloorz;
if ( !crossgap )
{
ssup = max(0,(pos.z-floorz));
SetZ(floorz);
lastground = player.onground = true;
}
}
if ( player.onground ) lastgroundtic = level.maptime;
else lastairtic = level.maptime;
if ( !(player.cheats & CF_PREDICTING) && !(player.cmd.forwardmove|player.cmd.sidemove) )
PlayIdle();
Vector3 dodge = (0,0,0), x, y, z;
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
int fm = player.cmd.forwardmove;
int sm = player.cmd.sidemove;
if ( !(fm|sm) ) fm = 1;
if ( fm ) dodge += (fm>0)?X:-X;
if ( sm ) dodge += (sm>0)?Y:-Y;
if ( player.cmd.buttons&BT_CROUCH ) dodge = (0,0,-1); // death from above
if ( dodge == (0,0,0) )
{
if ( !player.onground || bNOGRAVITY || (player.cheats&CF_NOCLIP2) ) dodge = X;
else dodge.xy = RotateVector((1,0),angle);
}
if ( player.onground && !bNOGRAVITY && !(player.cheats&CF_NOCLIP2) )
{
dodge.z = max(0,dodge.z);
if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z);
}
if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && (player.onground || level.IsJumpingAllowed() || (player.cmd.buttons&BT_CROUCH)) && (gamestate == GS_LEVEL) )
{
fullfuel = (dashfuel >= default.dashfuel);
dashdir = dodge.unit();
dashcooldown = 10;
dashboost = 20.;
bOnMobj = false;
if ( player.cheats & CF_REVERTPLEASE )
{
player.cheats &= ~CF_REVERTPLEASE;
player.camera = player.mo;
}
vel += dashdir*dashboost;
vel.z += clamp(-vel.z*.4,0.,30.);
player.jumptics = -1;
SetStateLabel("Dash");
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
lastbump *= .95;
mystats.dashcount++;
BumpView(5.,vel);
}
}
override void CheckJump()
{
if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not
if ( !(player.cmd.buttons&BT_JUMP) || (gamestate != GS_LEVEL) ) return;
Vector2 walldir = AngleToVector(angle);
bool walljump = false, wallclimb = false;
double climbvelz = 0.;
FLineTraceData d;
Actor jumpactor = null;
for ( int i=-4; i<int(height*.8); i++ )
{
if ( !walljump )
{
for ( int k=0; k<60; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = (angle-180)+k*j;
walljump = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( walljump )
{
jumpactor = d.HitActor;
walldir = (walldir*.7+AngleToVector(ang+180)*.3);
break;
}
}
if ( walljump ) break;
}
if ( walljump ) break;
}
if ( walljump ) break;
if ( !wallclimb )
{
for ( int k=0; k<30; k+=10 )
{
for ( int j=-1; j<=1; j+=2 )
{
double ang = angle+k*j;
wallclimb = LineTrace(ang,Radius+12,0,TRF_NOSKY|TRF_THRUHITSCAN|TRF_BLOCKSELF|TRF_SOLIDACTORS,i,data:d);
if ( wallclimb )
{
// double-check that it's climbable
if ( d.HitType == TRACE_HitWall )
{
if ( !d.HitLine.sidedef[1] || !!(d.HitLine.flags&(Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING|Line.ML_BLOCK_PLAYERS)) ) // nope
{
wallclimb = false;
break;
}
Sector s = d.HitLine.sidedef[!d.LineSide].sector;
double backfloorz = s.floorplane.ZAtPoint(d.HitLocation.xy);
double backceilz = s.ceilingplane.ZAtPoint(d.HitLocation.xy);
// cling to 3D floor if hit
if ( d.Hit3DFloor ) backfloorz = d.Hit3DFloor.top.ZAtPoint(d.HitLocation.xy);
if ( ((backceilz-backfloorz) < Height) || ((pos.z+Height+32) < backfloorz) ) // nope
{
wallclimb = false;
break;
}
climbvelz = 1.5*sqrt(max(1.,backfloorz-pos.z));
}
else if ( d.HitType == TRACE_HitActor )
{
if ( ((d.HitActor.ceilingz-d.HitActor.pos.z+d.HitActor.Height) < Height) || ((pos.z+Height+32) < (d.HitActor.pos.z+d.HitActor.Height)) ) // nope
{
wallclimb = false;
break;
}
climbvelz = 1.5*sqrt(max(1.,(d.HitActor.pos.z+d.HitActor.Height)-pos.z));
}
jumpactor = d.HitActor;
walldir = (walldir*.7+AngleToVector(ang)*.3);
break;
}
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
if ( wallclimb ) break;
}
// cooldown before we can do these, avoids accidental walljumps off ledges we just fell off from
if ( level.maptime < (lastgroundtic+4) )
{
walljump = false;
wallclimb = false;
jumpactor = null;
}
if ( player.crouchoffset ) player.crouching = 1;
else if ( bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return;
else if ( bNoGravity ) vel.z = 3;
else if ( level.IsJumpingAllowed()
&& ((player.onground && (player.jumptics == 0))
|| (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) )
{
if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) )
return;
double jumpvelz = JumpZ*35./GameTicRate;
double jumpfac = 0;
for ( let p=Inv; p; p=p.Inv )
{
let pp = PowerHighJump(p);
if ( pp )
{
double f = pp.Strength;
if ( f > jumpfac ) jumpfac = f;
}
}
if ( jumpfac > 0 ) jumpvelz *= jumpfac;
bool raging = FindInventory("RagekitPower");
if ( raging ) jumpvelz *= 2.;
double pvelz = vel.z;
if ( !player.onground && !(player.cheats&CF_PREDICTING) )
{
// check for wall stuff
if ( walljump )
{
if ( vel.z < 10. )
vel.z += 2.*jumpvelz+clamp(-pvelz*.6,0.,30.);
vel.xy += walldir*20*Speed;
lastbump *= .95;
}
else if ( wallclimb )
{
if ( vel.z < 10. )
vel.z += climbvelz+clamp(-pvelz*.95,0.,30.);
vel.xy += walldir*10*Speed;
lastbump *= .97;
}
if ( walljump && jumpactor && jumpactor.bSHOOTABLE )
{
SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000);
int dmg = jumpactor.DamageMobj(self,self,10,'Jump');
if ( raging )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = dmg;
}
}
if ( walljump || wallclimb )
{
last_kick = level.maptime+1;
SWWMUtility.AchievementProgressInc("jump",1,player);
}
}
bOnMobj = false;
player.jumpTics = -1;
if ( (dashfuel > 10.) && !player.onground && !walljump && !wallclimb )
{
dashboost = 3.;
boostcooldown = 20;
if ( vel.z < 10. )
vel.z += jumpvelz+clamp(-pvelz*.4,0.,30.);
A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP);
lastbump *= .95;
mystats.boostcount++;
last_boost = level.maptime+1;
BumpView(3.,vel);
SetStateLabel("Boost");
}
else
{
A_StartSound(walljump?"demolitionist/kick":"demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP);
dashboost = 0.;
// bunnyhop time
if ( !walljump && !wallclimb )
{
if ( !bWalking && (level.maptime < (lastairtic+10)) )
{
SWWMUtility.AchievementProgressInc("bune",1,player);
// bhop, z vel relative to vel size
if ( vel.z < 25. ) // don't ramp up too hard
{
vel.z += jumpvelz*(((player.cmd.buttons&BT_RUN)?1.2:.65)+vel.length()*.01);
// add part of last landing z velocity too
vel.z += max(0,-landvelz*(raging?.45:.35));
}
// accelerate
vel.xy += (RotateVector(NormalizedMove(),angle)/2400.)*(1.+vel.length()*.025)*TweakSpeed();
}
else
{
// first jump
if ( vel.z < 10. ) // don't ramp up too hard
{
vel.z += jumpvelz*(bWalking?.75:(player.cmd.buttons&BT_RUN)?1.25:1.);
// add part of last landing z velocity too
vel.z += max(0,-landvelz*(raging?.35:.25));
}
// long jump if running/sprinting
if ( !walljump && !wallclimb && !bWalking )
vel.xy += (RotateVector(NormalizedMove(),angle)/1500.)*(raging?2.:1.)*TweakSpeed();
}
}
BumpView(clamp((vel.length()-10)/12.,1.,20.),vel);
if ( swwm_mutevoice < 4 )
{
int loudlv = swwm_voiceamp;
A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/jump",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
SetStateLabel("Jump");
}
}
last_jump_held = level.maptime+1;
}
bool AllowCrouch()
{
if ( player.cmd.buttons&BT_JUMP ) return false;
if ( InStateSequence(CurState,FindState("Dash")) ) return false; // no crouch during dash
return true;
}
// Imagine having to duplicate two functions only to change a couple values in both
// I sure love constants
override void CrouchMove( int direction )
{
double defaultheight = FullHeight;
double savedheight = Height;
double crouchspeed = direction*CROUCHSPEED*.8; // slow down slightly so it matches the animation
double oldheight = player.viewheight;
player.crouchdir = direction;
player.crouchfactor += crouchspeed;
// check whether the move is ok
Height = defaultheight; // actually test the full height, or it'll look weird
if ( !TryMove(pos.xy,false) )
{
Height = savedheight;
if ( direction > 0 )
{
// doesn't fit
player.crouchfactor -= crouchspeed;
player.crouchdir = -1; // force crouch
return;
}
}
Height = savedheight;
player.crouchfactor = clamp(player.crouchfactor,.3,1.);
player.viewheight = ViewHeight*player.crouchfactor;
player.crouchviewdelta = player.viewheight-ViewHeight;
// Check for eyes going above/below fake floor due to crouching motion.
CheckFakeFloorTriggers(pos.z+oldheight,true);
}
override void CheckCrouch( bool totallyfrozen )
{
// crouch to swim/float down
if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) && bNOGRAVITY )
vel.z = -3;
bool wascrouching = !!(player.cmd.buttons&BT_CROUCH);
if ( !AllowCrouch() ) player.cmd.buttons &= ~BT_CROUCH;
if ( CanCrouch() && (player.health > 0) && level.IsCrouchingAllowed() )
{
if ( !totallyfrozen )
{
int crouchdir = player.crouching;
if ( !crouchdir ) crouchdir = (player.cmd.buttons&BT_CROUCH)?-1:1;
else if ( player.cmd.buttons&BT_CROUCH ) player.crouching = 0;
if ( (crouchdir == 1) && (player.crouchfactor < 1) && (pos.z+height < ceilingz) )
CrouchMove(1);
else if ( (crouchdir == -1) && (player.crouchfactor > .3) )
CrouchMove(-1);
}
}
else player.Uncrouch();
player.crouchoffset = -(ViewHeight)*(1-player.crouchfactor);
// we need the crouch button state to be preserved for other functions
if ( wascrouching ) player.cmd.buttons |= BT_CROUCH;
}
// let's customize our gravity
override void FallAndSink( double grav, double oldfloorz )
{
if ( !player || (player.mo != self) || (player.cheats&CF_TOTALLYFROZEN) )
{
Super.FallAndSink(grav,oldfloorz);
return;
}
// do nothing if standing on ground or "floating"
if ( player.onground || bNOGRAVITY ) return;
// ensure we don't pass terminal velocity just from falling
if ( vel.z < -50 ) return;
// we don't care about "the doom way" here, gravity is
// ALWAYS in effect when not standing on solid ground
if ( waterlevel > 1 )
{
// sink faster
grav *= .35;
}
// reduce gravity while we're boosting
else if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Boost")) )
grav *= .25;
vel.z -= grav;
}
}

View file

@ -0,0 +1,332 @@
// playerthink / deaththink
extend Class Demolitionist
{
void CheckDefaceTexture()
{
if ( player.usedown )
return;
FLineTraceData d;
LineTrace(angle,DEFMELEERANGE*2,pitch,TRF_THRUACTORS,player.viewheight,data:d);
if ( d.HitType == TRACE_HitNone ) return;
bool remove;
TextureID replacewith;
[remove, replacewith] = SWWMUtility.DefaceTexture(d.HitTexture);
if ( !remove ) return;
if ( (d.HitType != TRACE_HitWall) || !d.HitLine.special || !(d.HitLine.activation&SPAC_Use) )
player.usedown = true;
A_StartSound("bestsound",CHAN_ITEMEXTRA,CHANF_OVERLAP);
lastbump *= .97;
int scr = (TexMan.GetName(d.HitTexture).Left(6)~=="ZZWOLF")?200:20;
if ( scr == 20 ) SWWMUtility.AchievementProgressInc("doodle",1,player);
SWWMCredits.Give(player,scr);
if ( player == players[consoleplayer] ) SWWMScoreObj.Spawn(scr,d.HitLocation);
if ( d.HitType == TRACE_HitWall )
{
if ( d.Hit3DFloor )
{
// TODO connected textures for upper/lower
if ( d.Hit3DFloor.flags&F3DFloor.FF_UPPERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(0,replacewith);
else if ( d.Hit3DFloor.flags&F3DFloor.FF_LOWERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(2,replacewith);
else d.Hit3DFloor.master.sidedef[0].SetTexture(1,replacewith);
}
else
{
// find connected sidedefs with the same texture
Array<Line> con;
con.Clear();
con.Push(d.HitLine);
Sector s = d.LineSide?d.HitLine.backsector:d.HitLine.frontsector;
int found = 0;
do
{
found = 0;
foreach ( l:s.Lines )
{
if ( !l.sidedef[d.LineSide] || (l.sidedef[d.LineSide].GetTexture(d.LinePart) != d.HitTexture) )
continue;
if ( con.Find(l) < con.Size() ) continue;
bool notmatched = true;
foreach ( c:con )
{
if ( (l.v1 != c.v1) && (l.v2 != c.v2) && (l.v1 != c.v2) && (l.v2 != c.v1) )
continue;
notmatched = false;
break;
}
if ( notmatched ) continue;
con.Push(l);
found++;
}
}
while ( found > 0 );
foreach ( c:con )
c.sidedef[d.LineSide].SetTexture(d.LinePart,replacewith);
}
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor )
{
if ( d.Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) d.Hit3DFloor.model.SetTexture(1,replacewith);
else d.Hit3DFloor.model.SetTexture(0,replacewith);
}
else
{
// find connected sectors with the same ceiling texture (THIS IS VERY UGLY CODE)
Array<Sector> con;
con.Clear();
con.Push(d.HitSector);
int found;
do
{
found = 0;
foreach ( s:con )
{
foreach ( l:s.Lines )
{
// only check two-sided
if ( !l.sidedef[1] ) continue;
// don't check if there's a height difference
if ( (l.frontsector.ceilingplane.ZAtPoint(l.v1.p) != l.backsector.ceilingplane.ZAtPoint(l.v1.p))
|| (l.frontsector.ceilingplane.ZAtPoint(l.v2.p) != l.backsector.ceilingplane.ZAtPoint(l.v2.p)) )
continue;
if ( (l.frontsector.GetTexture(1) == d.HitTexture) && (con.Find(l.frontsector) >= con.Size()) )
{
found++;
con.Push(l.frontsector);
}
if ( (l.backsector.GetTexture(1) == d.HitTexture) && (con.Find(l.backsector) >= con.Size()) )
{
found++;
con.Push(l.backsector);
}
}
}
}
while ( found > 0 );
foreach ( s:con )
s.SetTexture(1,replacewith);
}
}
else if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor )
{
if ( d.Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) d.Hit3DFloor.model.SetTexture(0,replacewith);
else d.Hit3DFloor.model.SetTexture(1,replacewith);
}
else
{
// find connected sectors with the same floor texture (THIS IS VERY UGLY CODE)
Array<Sector> con;
con.Clear();
con.Push(d.HitSector);
int found;
do
{
found = 0;
foreach ( s:con )
{
foreach ( l:s.Lines )
{
// only check two-sided
if ( !l.sidedef[1] ) continue;
// don't check if there's a height difference
if ( (l.frontsector.floorplane.ZAtPoint(l.v1.p) != l.backsector.floorplane.ZAtPoint(l.v1.p))
|| (l.frontsector.floorplane.ZAtPoint(l.v2.p) != l.backsector.floorplane.ZAtPoint(l.v2.p)) )
continue;
if ( (l.frontsector.GetTexture(0) == d.HitTexture) && (con.Find(l.frontsector) >= con.Size()) )
{
found++;
con.Push(l.frontsector);
}
if ( (l.backsector.GetTexture(0) == d.HitTexture) && (con.Find(l.backsector) >= con.Size()) )
{
found++;
con.Push(l.backsector);
}
}
}
}
while ( found > 0 );
foreach ( s:con )
s.SetTexture(0,replacewith);
}
}
}
void CheckItemUsePickup()
{
if ( player.usedown )
return;
if ( !itrace ) itrace = new("SWWMItemTracer");
Vector3 x, y, z, dir;
[x, y, z] = SWWMUtility.GetPlayerAxes(self);
Vector3 origin = SWWMUtility.GetPlayerEye(self);
Sector os = level.PointInSector(origin.xy);
int rings = 1;
Array<Actor> ignoreme;
ignoreme.Clear();
for ( double i=0; i<.2; i+=.02 )
{
for ( int j=0; j<360; j+=(360/rings) )
{
dir = SWWMUtility.ConeSpread(x,y,z,j,i);
itrace.Trace(origin,os,dir,UseRange,0);
if ( itrace.Results.HitType != TRACE_HitActor ) continue;
if ( ignoreme.Find(itrace.Results.HitActor) < ignoreme.Size() ) continue;
player.usedown = true; // we found an item, ignore further uses
if ( itrace.Results.HitActor.Used(self) ) return;
ignoreme.Push(itrace.Results.HitActor);
}
rings += 2;
}
}
override void PlayerThink()
{
oldangle = angle;
oldpitch = pitch;
if ( player && (player.mo == self) && (player.playerstate != PST_DEAD) && (player.cmd.buttons&BT_USE) )
{
if ( !player.usedown ) lastuse = gametic;
CheckDefaceTexture();
if ( !player.usedown && froggy )
player.usedown = froggy.Used(self);
// try to "use" the item closest to the crosshair
CheckItemUsePickup();
}
Super.PlayerThink();
if ( (gametic == lastuse) && IsActorPlayingSound(CHAN_VOICE,"*usefail") )
{
failcounter++;
if ( (failcounter > 8) && !Random[DemoLines](0,max(0,12-failcounter/3)) && (gametic > failcooldown) && (swwm_mutevoice < 2) )
{
failcooldown = SWWMHandler.AddOneliner("usefail",2,20);
failcounter = max(4,failcounter-10);
}
else if ( (failcounter > 2) && Random[DemoLines](0,1) && (gametic > failcooldown) && (swwm_mutevoice < 4) )
{
int loudlv = swwm_voiceamp;
A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
failcooldown = int(S_GetLength(String.Format("voice/%s/usegrunt",myvoice.GetString()))*GameTicRate);
failcounter = max(2,failcounter-1);
}
}
else if ( gametic > lastuse+50 ) failcounter = 0;
oldlagangle = lagangle;
oldlagpitch = lagpitch;
lagangle = lagangle*.8+angle*.2;
lagpitch = lagpitch*.8+pitch*.2;
if ( !player || (player.mo != self) ) return;
if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) )
{
// faster falloff
player.jumptics -= 5;
if ( player.onground && (player.jumptics < -18) )
player.jumptics = 0;
}
if ( (player.playerstate != PST_DEAD) && !ReactionTime )
{
// quick grenade
if ( player.cmd.buttons&BT_USER4 )
SWWMGesture.SetGesture(self,GS_Grenade);
// emergency melee with no weapon
else if ( !player.ReadyWeapon && (player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_USER1)) )
SWWMGesture.SetGesture(self,GS_EmptyMelee);
}
}
override void DeathThink()
{
player.Uncrouch();
TickPSprites();
player.onground = (pos.Z<=floorz);
// ded (demo-chan falls faster tho)
player.deltaviewheight = 0;
if ( player.viewheight > 6 ) player.viewheight -= 3;
if ( player.viewheight < 6 ) player.viewheight = 6;
// center pitch
double dpitch = clamp(deltaangle(pitch,0),-6,6);
if ( abs(dpitch) < 3. ) pitch = 0.;
else A_SetPitch(pitch+dpitch,SPF_INTERPOLATE);
// add roll
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
if ( abs(droll) < 2. ) roll = 50.;
else A_SetRoll(roll+droll,SPF_INTERPOLATE);
player.mo.CalcHeight();
if ( player.damagecount ) player.damagecount--;
if ( player.poisoncount ) player.poisoncount--;
// solid unless we can respawn, for safety
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
bSolid = false;
else bSolid = true;
if ( player.viewheight <= 6 )
{
deadtimer++;
if ( (deadtimer == 60) && (player == players[consoleplayer]) )
A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI);
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (((player.cmd.buttons&BT_USE) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn)
&& ((Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot)) )
{
player.cls = null;
player.playerstate = PST_REBORN;
if ( special1 > 2 ) special1 = 0;
}
}
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) )
{
// reload save
player.cls = null;
player.playerstate = PST_ENTER;
if ( special1 > 2 ) special1 = 0;
}
else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 120) && !FindInventory("SWWMReviveDisabler") && swwm_revive )
{
// reboot (if possible)
if ( !FindInventory("ReviveCooldown") && (((swwm_revivecooldown >= 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4)) || !hasrevived) )
{
if ( hasrevived ) SWWMUtility.MarkAchievement("sekiro",player);
hasrevived = true;
player.Resurrect();
player.damagecount = 0;
player.bonuscount = 0;
player.poisoncount = 0;
blinktime = 30;
SetState(FindState("Spawn")+1); // skip tweening
roll = 0;
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = 30;
ReactionTime = 17;
A_Stop();
A_AlertMonsters(swwm_uncapalert?0:2500);
if ( player == players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
}
lastbump *= 1.5;
SWWMHandler.DoFlash(self,Color(255,255,255,255),10);
SWWMHandler.DoFlash(self,Color(255,128,192,255),30);
if ( special1 > 2 ) special1 = 0;
if ( (swwm_revivecooldown > 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4) )
GiveInventory("ReviveCooldown",1);
}
else if ( level.maptime > revivefail )
{
if ( player == players[consoleplayer] ) A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI);
revivefail = level.maptime+120;
}
}
}
else deadtimer = 0;
}
}

View file

@ -0,0 +1,843 @@
// Tick and friends
extend Class Demolitionist
{
void SenseItems()
{
if ( player.cmd.buttons&BT_USER3 )
{
let bt = BlockThingsIterator.Create(self,800);
while ( bt.Next() )
{
let i = bt.Thing;
if ( !i || (!(i is 'Inventory') && !(i is 'Chancebox') && !(i is 'SWWMRespawnTimer')) ) continue;
if ( (i is 'Inventory') && (i.bINVISIBLE || !i.bSPECIAL || Inventory(i).Owner) ) continue;
if ( (i is 'Chancebox') && (i.CurState != i.SpawnState) ) continue;
if ( !SWWMUtility.SphereIntersect(i,pos,800) ) continue;
if ( !level.allmap && !i.CheckSight(self,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
SWWMItemSense.Spawn(self,i);
}
bt.Destroy();
}
SWWMItemsense itm = itemsense;
SWWMItemsense prev = null, next;
while ( itm )
{
next = itm.next;
if ( itm.Tick() )
{
if ( prev ) prev.next = next;
else itemsense = next;
itm.Destroy();
}
else prev = itm;
itm = next;
}
}
void CheckItemMagnet()
{
if ( magtime > 40 )
{
if ( !(player.cmd.buttons&BT_USE) || swwm_usetopickup )
{
magtime = 0;
SWWMMagItem mi = magitem;
while ( mi )
{
let next = mi.next;
mi.Destroy();
mi = next;
}
magitem_cnt = 0;
return;
}
if ( (magitem_cnt < 8) && !swwm_usetopickup )
{
let bt = BlockThingsIterator.Create(self,500);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !(t is 'Inventory') || !t.bSPECIAL || !t.bDROPPED || t.bINVISIBLE || Inventory(t).Owner || !SWWMUtility.SphereIntersect(t,pos,500) || !CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
let i = Inventory(t);
Class<Inventory> cls = i.GetClass();
if ( i is 'Ammo' ) cls = Ammo(i).GetParentAmmo();
else if ( i is 'MaGammo' ) cls = MagAmmo(i).GetParentMagAmmo();
let oi = FindInventory(cls);
if ( !i.bALWAYSPICKUP && oi && (oi.Amount >= oi.MaxAmount) ) continue;
if ( (i is 'SWWMWeapon') && SWWMWeapon(i).HasSwapWeapon(self) && swwm_swapweapons ) continue;
if ( (i is 'SWWMDualWeaponGiver') && SWWMDualWeaponGiver(i).HasSwapWeapon(self) && swwm_swapweapons ) continue;
bool addme = true;
for ( SWWMMagItem mi=magitem; mi; mi=mi.next )
{
if ( mi.item != i ) continue;
addme = false;
break;
}
if ( !addme ) continue;
let nmi = new("SWWMMagItem");
nmi.target = self;
nmi.item = i;
nmi.next = magitem;
i.A_StartSound("misc/magitem",CHAN_AMBEXTRA,CHANF_LOOP,.2,1.,.5);
i.bNOGRAVITY = true;
magitem = nmi;
magitem_cnt++;
}
bt.Destroy();
}
SWWMMagItem itm = magitem;
SWWMMagItem prev = null, next;
while ( itm )
{
next = itm.next;
if ( itm.Tick() )
{
if ( prev ) prev.next = next;
else magitem = next;
itm.Destroy();
magitem_cnt--;
}
else prev = itm;
itm = next;
}
return;
}
if ( player.cmd.buttons&BT_USE ) magtime++;
}
void CheckUnderwaterAmb( bool restore = false )
{
Vector3 headpos = Vec3Offset(0,0,player.viewheight);
Vector3 centerpos = Vec3Offset(0,0,height/2);
Sector headregion = null;
if ( CurSector.moreflags&Sector.SECMF_UNDERWATER ) // check underwater sector
headregion = CurSector;
else if ( CurSector.heightsec && (Cursector.heightsec.moreflags&Sector.SECMF_UNDERWATERMASK) ) // check height transfer
{
let hsec = CurSector.heightsec;
double fh = hsec.floorplane.ZAtPoint(pos.xy);
if ( pos.z < fh )
{
if ( headpos.z <= fh )
headregion = hsec;
}
else if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY) && (headpos.z > hsec.ceilingplane.ZAtPoint(pos.xy)) )
headregion = hsec;
}
else // check 3D floors
{
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
let ff = CurSector.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || !(ff.flags&F3DFloor.FF_SWIMMABLE) ) continue;
double ff_bottom = ff.bottom.ZAtPoint(pos.xy);
double ff_top = ff.top.ZAtPoint(pos.xy);
if ( (ff_top <= pos.z) || (ff_bottom > centerpos.z) ) continue;
if ( headpos.z <= ff_top )
headregion = ff.model;
break;
}
}
int curunder = UNDER_NONE;
if ( headregion )
{
switch ( headregion.damagetype )
{
case 'Fire':
case 'Lava':
curunder = UNDER_LAVA;
break;
case 'Slime':
case 'Poison':
case 'PoisonCloud':
curunder = UNDER_SLIME;
break;
case 'Ice':
case 'Drowning':
default:
curunder = UNDER_WATER;
break;
}
undercol = headregion.ColorMap.LightColor;
}
if ( (curunder != lastunder) || restore )
{
static const string undersnd[] = {"","misc/underwater","misc/underslime","misc/underlava"};
static const string entersnd[] = {"","misc/waterenter","misc/slimeenter","misc/lavaenter"};
static const string exitsnd[] = {"","misc/waterexit","misc/slimeexit","misc/lavaexit"};
A_StopSound(CHAN_AMBEXTRA);
if ( curunder > UNDER_NONE )
{
A_StartSound(undersnd[curunder],CHAN_AMBEXTRA,CHANF_LOOP|CHANF_UI);
if ( !restore && (players[consoleplayer].Camera == self) )
A_StartSound(entersnd[curunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
}
if ( !restore && (lastunder > UNDER_NONE) && (players[consoleplayer].Camera == self) )
A_StartSound(exitsnd[lastunder],CHAN_FOOTSTEP,CHANF_OVERLAP|CHANF_UI);
}
if ( curunder > UNDER_NONE )
A_SoundVolume(CHAN_AMBEXTRA,(players[consoleplayer].Camera==self)?1.:0.);
lastunder = curunder;
}
override void Tick()
{
Vector3 oldpos = pos;
// can't be poisoned
PoisonDurationReceived = 0;
PoisonPeriodReceived = 0;
PoisonDamageReceived = 0;
Super.Tick();
if ( (gamestate != GS_LEVEL) || !player || (player.mo != self) || (freezetics > 0) ) return;
UpdateFace();
UpdateTags();
if ( hasteleported )
{
// we just got teleported, don't count the travel distance
oldpos = pos;
hasteleported = false;
}
if ( !selflight )
{
selflight = new("DemolitionistSelfLight");
selflight.ChangeStatNum(STAT_USER);
selflight.target = self;
selflight.Tick();
}
if ( !myshadow ) myshadow = SWWMShadow.Track(self);
// double-check that we have these
if ( !FindInventory("AlmasteelPlating") )
{
let ap = Inventory(Spawn("AlmasteelPlating"));
if ( !ap.CallTryPickup(self) ) ap.Destroy();
}
if ( !FindInventory("SayaCollar") )
{
let sc = Inventory(Spawn("SayaCollar"));
if ( !sc.CallTryPickup(self) ) sc.Destroy();
}
// this is why we need mod cvar callbacks
if ( swwm_singlefirst != oldsinglefirst )
WeaponSlots.SetupWeaponSlots(self);
oldsinglefirst = swwm_singlefirst;
// overheal fading
if ( !isFrozen() && !(player.cheats&CF_TOTALLYFROZEN) )
{
if ( (health <= 200) || (health > oldhealth) )
{
healtimer = 0;
healcooldown = 80;
}
else if ( health > 200 )
{
if ( healcooldown > 0 ) healcooldown--;
else
{
if ( health > 1000 )
{
let spr = DivineSpriteEffect(FindInventory("DivineSpriteEffect"));
if ( !spr || spr.bHealDone )
A_SetHealth(max(1000,health-10));
if ( health <= 1000 ) healcooldown = 40;
}
else if ( health > 500 )
{
if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%3) )
A_SetHealth(health-1);
if ( health <= 500 ) healcooldown = 20;
}
else if ( health > 200 )
{
if ( !FindInventory("RefresherRegen") && !(healtimer%12) )
A_SetHealth(health-1);
}
healtimer++;
}
}
}
oldhealth = health;
oldlagvel = lagvel;
oldlagready = lagready;
if ( player.weaponstate&WF_WEAPONBOBBING ) lagready = lagready*.9+.1;
else lagready = lagready*.4;
lagvel = lagvel*.8+vel*.2;
double traveldist = level.Vec3Diff(oldpos,pos).length();
if ( !player.onground || bNoGravity )
{
if ( waterlevel > 1 )
{
cairtime = 0;
mystats.swimdist += traveldist;
}
else
{
cairtime++;
if ( cairtime > mystats.airtime ) mystats.airtime = cairtime;
mystats.airdist += traveldist;
}
if ( (vel.z < -fallingscreamminspeed) && (vel.z > -fallingscreammaxspeed) && (player == players[consoleplayer]) )
SWWMHandler.AddOneliner("falling",2,30);
}
else
{
SWWMHandler.CancelOneliner("falling");
airscreamtime = 0;
cairtime = 0;
mystats.grounddist += traveldist;
SWWMUtility.AchievementProgressIncDouble("travel",traveldist/32000.,player);
}
// spawn bubbles while underwater
if ( (waterlevel > 1) && !Random[ExploS](0,5) )
{
int numpt = Random[ExploS](-2,2);
for ( int i=0; i<numpt; i++ )
{
let p = Spawn("SWWMBubble",Vec3Offset(FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](height*.1,height*.9)));
p.scale *= FRandom[ExploS](.02,.2);
p.vel += vel*.2;
}
}
CheckUnderwaterAmb();
SenseItems();
CheckItemMagnet();
if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length();
if ( vel.length() > ((3600*GameTicRate)/32000.) )
SWWMUtility.AchievementProgress("sanic",int((vel.length()*3600*GameTicRate)/32000.),player);
if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player);
if ( player.onground && !bNoGravity && !lastground )
{
// bump down weapon
bumpvelz -= lastvelz;
BumpView(min(-lastvelz/10.,20));
if ( lastvelz < -25 )
{
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = int(-lastvelz);
A_AlertMonsters(swwm_uncapalert?0:2500);
BumpView(15.);
lastbump *= 1.1;
if ( FindInventory("RagekitPower") )
{
// stop for just a split second UNLESS bunnyhopping
if ( !(player.cmd.buttons&BT_RUN) || (level.maptime >= (lastairtic+10)) )
ReactionTime = 6;
}
else
{
A_Stop();
ReactionTime = 17;
}
if ( player == players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
}
mystats.stompcount++;
}
if ( lastvelz < -10 )
A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP);
if ( (lastvelz < -gruntspeed) && (swwm_mutevoice < 4) && (health > 0) )
{
int loudlv = swwm_voiceamp;
A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP);
if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX,CHANF_OVERLAP);
if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP);
if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP);
}
if ( lastvelz < -1 )
A_Footstep(0,1,clamp(-lastvelz*0.05,0.0,1.0),true);
// bounce off slopes
if ( pos.z <= floorz )
{
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 fnorm;
if ( ff ) fnorm = -ff.top.Normal;
else fnorm = FloorSector.floorplane.Normal;
double maxsteep = clamp(abs(lastvelz)*.1,0.,.9);
if ( fnorm.z < maxsteep ) vel = .8*fnorm*(-lastvelz)*(1.-.8*fnorm.z);
}
}
// crush anything we're standing on
bool dummy;
Actor encroached;
[dummy, encroached] = TestMobjZ();
// add special check so corpses don't get stuck on top of monsters and players
if ( encroached && encroached.bSOLID && (bSOLID || encroached.bACTLIKEBRIDGE) )
{
if ( (encroached is 'FroggyChair') && (encroached != oldencroached) )
encroached.A_StartSound("squeak",CHAN_BODY,CHANF_OVERLAP);
// try to follow movement (this method is awkward but works with monsters)
if ( encroached == oldencroached )
{
Vector3 oldp = pos;
Vector3 newp = level.Vec3Offset(pos,level.Vec3Diff(oldencroachedpos,encroached.pos));
if ( level.IsPointInLevel(newp) )
{
SetOrigin(newp,true);
if ( !TestMobjLocation() ) SetOrigin(oldp,true);
}
encroachtics++;
if ( !(encroachtics%GameTicRate) && (encroached.bISMONSTER || encroached.player || (encroached is 'ScriptedMarine')) && !IsFriend(encroached) )
SWWMUtility.AchievementProgress("step",encroachtics/GameTicRate,player);
}
else encroachtics = 0;
oldencroached = encroached;
oldencroachedpos = encroached.pos;
}
else
{
// launch in movement direction (useful for moving platforms)
// make sure we're not getting launched because an enemy just died under our feet, because that can cause some issues
if ( oldencroached && (dashboost <= 0.) && (lastvelz >= -25) && (!oldencroached.bISMONSTER || (oldencroached.Health > 0)) ) vel += oldencroached.vel+level.Vec3Diff(oldencroachedpos,oldencroached.pos);
oldencroached = null;
encroachtics = 0;
}
if ( encroached && encroached.bSHOOTABLE && !encroached.bNODAMAGE && (lastvelz <= 0) && !(encroached is 'Demolitionist') )
{
if ( (lastvelz < -5) || !(level.maptime%5) )
{
int realdmg = encroached.DamageMobj(self,self,int((2+max(0,-lastvelz*3))*max(1.,mass/encroached.mass)),'Jump',DMG_THRUSTLESS);
if ( FindInventory("RagekitPower") )
{
let ps = Spawn("BigPunchSplash",pos);
ps.damagetype = 'Jump';
ps.target = self;
ps.special1 = realdmg;
}
if ( (realdmg > 0) && encroached && !encroached.bNOBLOOD && !encroached.bINVULNERABLE )
{
encroached.TraceBleed(realdmg,self);
encroached.SpawnBlood(pos,angle,realdmg);
}
}
}
if ( abs(bumpvelz) > double.epsilon )
{
lagvel.z += bumpvelz*.2;
bumpvelz *= .8;
}
if ( abs(bumpangle) > double.epsilon )
{
ViewAngle += bumpangle*.5;
bumpangle *= .8;
}
if ( abs(bumppitch) > double.epsilon )
{
ViewPitch += bumppitch*.5;
bumppitch *= .8;
}
if ( abs(bumproll) > double.epsilon )
{
ViewRoll += bumproll*.5;
bumproll *= .8;
}
// stabilize view angles
ViewAngle *= .8;
ViewPitch *= .8;
ViewRoll *= .8;
player.cheats |= CF_INTERPVIEWANGLES;
if ( player.onground && !lastground ) landvelz = lastvelz;
else if ( !player.onground && lastground ) landvelz = 0;
else if ( player.onground && lastground ) landvelz *= .9;
lastground = player.onground;
lastvelz = prevvelz;
prevvelz = vel.z;
bool isdashing = InStateSequence(CurState,FindState("Dash"));
bool isboosting = InStateSequence(CurState,FindState("Boost"));
bNOFRICTION = ((bFly&&!bFlyCheat&&!(player.cheats&CF_NOCLIP2))||isdashing);
if ( fuelcooldown == 1 ) A_StartSound("demolitionist/fuelregen",CHAN_FUELREGEN,CHANF_LOOP,.35,4.,.5);
else if ( fuelcooldown > 1 ) A_StopSound(CHAN_FUELREGEN);
fuelcooldown = max(0,fuelcooldown-1);
if ( dashlockst > 0 )
{
dashlockst--;
if ( dashlockst == 0 ) A_StartSound("demolitionist/dashregen",CHAN_BODY,CHANF_OVERLAP,.5,4.);
}
else dashcooldown = max(0,dashcooldown-1);
boostcooldown = max(0,boostcooldown-1);
if ( (fuelcooldown <= 0) && (dashfuel < default.dashfuel) )
{
A_SoundPitch(CHAN_FUELREGEN,.5+1.5*((dashfuel/default.dashfuel)**2.));
double oldfuel = dashfuel;
dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.));
// stops
if ( (oldfuel < (default.dashfuel/24)) && (dashfuel >= default.dashfuel/24) )
{
dashfuel = default.dashfuel/24;
fuelcooldown = 20;
A_StartSound("demolitionist/fuelrgstp",CHAN_BODY,CHANF_OVERLAP,.6,4.,.6);
}
else if ( (oldfuel < (default.dashfuel/12)) && (dashfuel >= default.dashfuel/12) )
{
dashfuel = default.dashfuel/12;
fuelcooldown = 10;
A_StartSound("demolitionist/fuelrgstp",CHAN_BODY,CHANF_OVERLAP,.6,4.,.7);
}
if ( (oldfuel < dashfuel) && (dashfuel == default.dashfuel) )
{
A_StopSound(CHAN_FUELREGEN);
A_StartSound("demolitionist/fuelrgend",CHAN_BODY,CHANF_OVERLAP,.6,4.);
}
}
else if ( dashfuel >= default.dashfuel ) A_StopSound(CHAN_FUELREGEN);
if ( ((dashboost <= 0) || !(isdashing || (isboosting && player.cmd.buttons&BT_JUMP))) && IsActorPlayingSound(CHAN_JETPACK,"demolitionist/jet") )
A_StartSound("demolitionist/jetstop",CHAN_JETPACK);
PainChance = isdashing?0:255;
if ( isdashing || (vel.length() > 30) )
{
bool oldpush = bCANPUSHWALLS;
bool olduse = bCANUSEWALLS;
bool oldmcross = bACTIVATEMCROSS;
bool oldtele = bNOTELEPORT;
// needed to prevent the many TryMove calls from activating unwanted lines
bCANPUSHWALLS = false;
bCANUSEWALLS = false;
bACTIVATEMCROSS = false;
bNOTELEPORT = true;
Actor a;
if ( isdashing && (dashboost > 0.) )
{
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
{
a = Spawn("DashTrail",Vec3Angle(15,angle+i*140,35));
a.master = self;
a.vel = (RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
Vector3 dir = vel;
double spd = dir.length();
dir = dir/spd;
Vector3 viewdir = SWWMUtility.Vec3FromAngles(angle,pitch);
// look for things we could potentially bump into
bool bumped = false;
let bi = BlockThingsIterator.Create(self,500);
let raging = RagekitPower(FindInventory("RagekitPower"));
double maxmass = max(mass*spd/40.,200);
if ( raging ) maxmass *= 2;
while ( (spd > 0) && bi.Next() )
{
a = bi.Thing;
if ( !a || (a == self) || (!a.bSOLID && !a.bSHOOTABLE) || a.bTHRUACTORS || a.bCORPSE || !CanCollideWith(a,false) || !a.CanCollideWith(self,true) ) continue;
if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*(spd+radius),8) ) continue;
if ( (a.pos.z <= a.floorz) && (a.height <= MaxStepHeight) ) continue;
Vector3 diff = level.Vec3Diff(pos,a.pos);
Vector3 dirto = diff.unit();
if ( dir dot dirto < .1 ) continue;
if ( (diff.z <= -a.height) && (lastvelz < -25) ) continue;
if ( (diff.z <= -a.height) && !isdashing ) continue;
// don't bump bridges if hit at a specific angle
if ( a.bACTLIKEBRIDGE )
{
Vector3 bnorm = -dirto;
if ( diff.z <= -a.height ) continue; // no bump from above
else if ( diff.z >= Height ) bnorm = (0,0,-1);
else if ( diff.x > a.Radius+Radius ) bnorm = (-1,0,0);
else if ( diff.x < -(a.Radius+Radius) ) bnorm = (1,0,0);
else if ( diff.y > a.Radius+Radius ) bnorm = (0,-1,0);
else if ( diff.y < -(a.Radius+Radius) ) bnorm = (0,1,0);
if ( dir dot bnorm > -.6 ) continue;
}
if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
// large monsters will stop the player (unless hit from above if we're going at ground pound speed)
A_QuakeEx(4,4,4,10,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_AlertMonsters(swwm_uncapalert?0:800);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
a.A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(20+spd/4.);
lastbump *= .8;
if ( (a.bDONTTHRUST || a.bACTLIKEBRIDGE || (a.Mass >= maxmass) || (!a.bSHOOTABLE && !a.bPUSHABLE && (a.Health > 0))) && a.bSOLID && (dir dot dirto > .65) )
{
if ( bumped ) continue;
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel -= dirto*(10+(spd*50/mass));
vel.z += 5+(spd*(10/mass));
dashboost *= 0.;
}
Vector3 pushdir = dirto*.1+dir*.9;
if ( !a.bDONTTHRUST && (a.Mass < maxmass) && (a.bSHOOTABLE || a.bPUSHABLE) )
{
a.vel += pushdir*(25+(spd*20/max(50,a.mass)));
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
a.vel.z += 5+(spd*(5/max(50,a.mass)));
}
int flg = DMG_THRUSTLESS;
if ( raging ) flg |= DMG_FOILINVUL;
if ( !a.player && !a.bDONTBLAST ) a.bBLASTED = true;
int dmg = int(10+spd*3.);
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
dmg *= 3;
buttslam = true;
}
if ( a.bSHOOTABLE )
{
dmg = a.DamageMobj(self,self,dmg,buttslam?'Buttslam':'Dash',flg);
if ( (dmg > 0) && a && !a.bNOBLOOD && (raging || !a.bINVULNERABLE) )
{
a.TraceBleed(dmg,self);
a.SpawnBlood(level.Vec3Offset(pos,diff/2),atan2(dir.y,dir.x)+180,dmg);
}
if ( buttslam && (!a || (a.Health <= 0)) )
{
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
Spawn("SWWMItemFog",level.Vec3Offset(pos,diff/2));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
}
if ( raging )
{
let ps = Spawn("BigPunchSplash",level.Vec3Offset(pos,diff/2));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = dmg;
raging.DoHitFX();
}
}
// check for ceiling collision
if ( (spd > 0) && !bumped && ((pos.z+Height+dir.z*spd) >= ceilingz) )
{
F3DFloor ff;
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 ceilnorm;
if ( ff ) ceilnorm = ff.model.floorplane.Normal;
else ceilnorm = CeilingSector.ceilingplane.Normal;
// don't bump if we're only grazing it
if ( dir dot ceilnorm < -.6 )
{
bool busted = false;
if ( raging || swwm_omnibust )
{
// see if we can bust it
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = int(10+spd*3.);
if ( raging ) dmg *= 8;
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
dmg *= 3;
buttslam = true;
}
if ( ff ) tempme.Results.ffloor = ff;
tempme.Results.HitSector = CeilingSector;
tempme.Results.HitType = TRACE_HitCeiling;
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
{
// busted through
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
busted = true;
if ( buttslam )
{
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
Spawn("SWWMItemFog",Vec3Offset(0,0,Height));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
}
}
if ( !busted || !raging )
{
// headbump
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(20+spd/4.);
lastbump *= .8;
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_AlertMonsters(swwm_uncapalert?0:800);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel.z += 5+(spd*(10/mass));
vel += ceilnorm*(15+(spd*40/mass));
dashboost *= 0.;
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
}
}
}
// check for wall collision
FCheckPosition tm;
Vector2 steppy = dir.xy*spd/8.;
Vector3 oldpos = pos;
bool oldthru = bTHRUACTORS;
bTHRUACTORS = true;
for ( int i=1; i<=8; i++ )
{
if ( (spd > 0) && !bumped && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) && SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKEVERYTHING|Line.ML_BLOCKING|Line.ML_BLOCK_PLAYERS) )
{
Vector3 wallnorm = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
int lside = 1;
if ( !BlockingLine.sidedef[1] || !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
{
lside = 0;
wallnorm *= -1;
}
// don't bump if we're only grazing it
if ( dir dot wallnorm > -.6 )
continue;
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
buttslam = true;
// leave buttmark
Vector3 vdir = (2,0,Height*.54);
A_SprayDecal("ButtMark",172,SWWMUtility.RotateVector3(vdir,angle+90),dir);
A_SprayDecal("ButtMark",172,SWWMUtility.RotateVector3(vdir,angle-90),dir);
}
if ( raging || swwm_omnibust )
{
// see if we can bust it
let tempme = new("LineTracer"); // gross hack to pass needed data
int dmg = int(10+spd*3.);
if ( raging ) dmg *= 8;
if ( buttslam ) dmg *= 3;
tempme.Results.HitLine = BlockingLine;
tempme.Results.HitType = TRACE_HitWall;
tempme.Results.Side = lside;
tempme.Results.Tier = TIER_MIDDLE;
if ( BlockingLine.sidedef[1] )
{
double ceilz = BlockingLine.sidedef[!lside].sector.ceilingplane.ZAtPoint(pos.xy);
double florz = BlockingLine.sidedef[!lside].sector.floorplane.ZAtPoint(pos.xy);
if ( pos.z+Height >= ceilz )
tempme.Results.Tier = TIER_UPPER;
else if ( pos.z <= florz )
tempme.Results.Tier = TIER_LOWER;
}
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
{
// busted through
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
if ( buttslam )
{
A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_OVERLAP,1.,.4);
if ( swwm_buttsfx ) A_StartSound("demolitionist/buttslamx",CHAN_DAMAGE,CHAN_OVERLAP,1.,.2);
Spawn("SWWMItemFog",Vec3Offset(dir.x*radius,dir.y*radius,(tempme.Results.Tier==TIER_UPPER)?Height:(tempme.Results.Tier==TIER_LOWER)?0:(Height/2)));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
if ( raging ) continue; // don't stop
}
}
// wallbump
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(25+spd/4.);
lastbump *= .8;
A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_AlertMonsters(swwm_uncapalert?0:800);
vel *= .2;
vel -= dir*(10+(spd*30/mass));
vel += wallnorm*(10+(spd*50/mass));
vel.z += 5+(spd*(10/mass));
dashboost *= 0.;
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(dir.x*radius,dir.y*radius,Height/2.));
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
// activate it
int locknum = SWWMUtility.GetLineLock(BlockingLine);
if ( !locknum || CheckKeys(locknum,false,true) )
{
hitactivate = true;
BlockingLine.Activate(self,lside,SPAC_Use);
hitactivate = false;
}
BlockingLine.Activate(self,lside,SPAC_Impact);
break;
}
}
// check for slope boosting (only if dashing)
if ( (pos.z <= floorz) && (spd > 0) && isdashing )
{
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 fnorm;
if ( ff ) fnorm = -ff.top.Normal;
else fnorm = FloorSector.floorplane.Normal;
// how far up should the boost be
double xspd = spd*(1.-dir.z);
vel.z += xspd*(1.-fnorm.z);
}
SetOrigin(oldpos,true);
bTHRUACTORS = oldthru;
bCANPUSHWALLS = oldpush;
bCANUSEWALLS = olduse;
bACTIVATEMCROSS = oldmcross;
bNOTELEPORT = oldtele;
}
else if ( isboosting && (dashboost > 0.) )
{
Actor a;
for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ )
{
a = Spawn("DashTrail",Vec3Angle(10,angle+i*140,40));
a.master = self;
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
// Misc. Utility code
Class SWWMUtility
{
// gets the names of all mod cvars
static clearscope void GetCVars( out Array<String> cvarlist )
{
cvarlist.Clear();
let lmp = Wads.CheckNumForFullname("cvarinfo.base");
if ( lmp == -1 ) ThrowAbortException("'cvarinfo.base' not found");
String dat = Wads.ReadLump(lmp);
Array<String> list, ln;
// fucking Windows
dat.Replace("\r","");
list.Clear();
dat.Split(list,"\n");
foreach ( l:list )
{
if ( (l.Length() == 0) || (l.Left(2) == "//") || (l.Left(1) == "") )
continue;
int eq = l.IndexOf("=");
if ( eq == -1 ) continue;
l.Truncate(eq);
ln.Clear();
l.Split(ln," ",0);
foreach ( w:ln )
{
if ( (w.Left(5) != "swwm_") ) continue;
cvarlist.Push(w);
}
}
}
// sends
static clearscope void SendTooltip( Class<SWWMWeapon> which )
{
if ( !swwm_weapontooltips ) return;
CVar v = CVar.FindCVar('swwm_tooltipshown');
String tt = v.GetString();
Array<String> wpn;
tt.Split(wpn,",");
foreach ( w:wpn )
{
if ( w == which.GetClassName() ) return;
}
if ( tt == "" ) tt = which.GetClassName();
else tt = tt..","..which.GetClassName();
v.SetString(tt);
EventHandler.SendInterfaceEvent(consoleplayer,"swwmwpntooltip."..which.GetClassName());
v = CVar.FindCVar('swwm_tooltipnote');
if ( v.GetBool() ) return;
v.SetBool(true);
Console.Printf(StringTable.Localize("$SWWM_TTNOTE"));
}
static clearscope bool CheatsDisabled( int p = -1 )
{
if ( cl_blockcheats || ((G_SkillPropertyInt(SKILLP_DisableCheats) || netgame || deathmatch) && !sv_cheats) )
{
if ( (p != -1) && (p == consoleplayer) )
{
Console.Printf("\cxSORRY NOTHING\c-");
S_StartSound("misc/trombone",CHAN_VOICE,CHANF_UI);
}
return true;
}
return false;
}
}
Class EnvmapDebugSphere : Actor
{
override bool Used( Actor user )
{
if ( CurState.NextState )
SetState(CurState.NextState);
else SetState(SpawnState);
return true;
}
override void Tick() {}
Default
{
RenderStyle "Normal";
Radius 16;
Height 48;
}
States
{
Spawn:
XZW1 A -1 NoDelay A_SetRenderStyle(1.,STYLE_Normal);
XZW1 B -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 C -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 D -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 E -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 F -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 G -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 H -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 I -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 J -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 K -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 L -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 M -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 N -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 O -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
XZW1 P -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
XZW1 Q -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 R -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
XZW1 S -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 T -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 U -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 V -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 W -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 X -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 Y -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 Z -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW2 A -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW2 B -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW2 C -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW2 D -1 A_SetRenderStyle(1.,STYLE_Add);
XZW2 E -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
XZW2 F -1 Bright A_SetRenderStyle(1.,STYLE_Add);
Loop;
}
}

View file

@ -0,0 +1,86 @@
// achievement helpers
extend class SWWMUtility
{
static clearscope void MarkAchievement( String mvar, PlayerInfo p = null )
{
if ( !p || (p != players[consoleplayer]) ) return;
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
if ( !hnd ) return;
String val = hnd.achievementstate.At(mvar);
if ( val == "" )
{
if ( developer >= 2 ) Console.Printf("MarkAchievement: achievement '"..mvar.."' not found");
return;
}
if ( val == "2" ) return;
hnd.achievementstate.Insert(mvar,"1");
}
static clearscope int GetAchievementProgress( String pvar, PlayerInfo p = null )
{
if ( !p || (p != players[consoleplayer]) ) return 0;
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
if ( !hnd ) return 0;
String pval = hnd.achievementprogress.At(pvar);
if ( pval == "" )
{
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
return 0;
}
return pval.ToInt();
}
static clearscope void AchievementProgress( String pvar, int val, PlayerInfo p = null )
{
if ( !p || (p != players[consoleplayer]) ) return;
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
if ( !hnd ) return;
String pval = hnd.achievementprogress.At(pvar);
if ( pval == "" )
{
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
return;
}
if ( val <= pval.ToInt() ) return;
hnd.achievementprogress.Insert(pvar,String.Format("%d",val));
}
static clearscope void AchievementProgressInc( String pvar, int inc, PlayerInfo p = null )
{
if ( !p || (p != players[consoleplayer]) ) return;
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
if ( !hnd ) return;
String pval = hnd.achievementprogress.At(pvar);
if ( pval == "" )
{
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
return;
}
hnd.achievementprogress.Insert(pvar,String.Format("%d",pval.ToInt()+inc));
}
static clearscope void AchievementProgressIncDouble( String pvar, double inc, PlayerInfo p = null )
{
if ( !p || (p != players[consoleplayer]) ) return;
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
if ( !hnd ) return;
String pval = hnd.achievementprogress.At(pvar);
if ( pval == "" )
{
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
return;
}
hnd.achievementprogress.Insert(pvar,String.Format("%g",pval.ToDouble()+inc));
}
// for bitfields
static clearscope void AchievementProgressOr( String pvar, int val, PlayerInfo p = null )
{
if ( !p || (p != players[consoleplayer]) ) return;
let hnd = SWWMStaticHandler(StaticEventHandler.Find("SWWMStaticHandler"));
if ( !hnd ) return;
String pval = hnd.achievementprogress.At(pvar);
if ( pval == "" )
{
if ( developer >= 2 ) Console.Printf("AchievementProgress: achievement '"..pvar.."' not found");
return;
}
hnd.achievementprogress.Insert(pvar,String.Format("%d",pval.ToInt()|val));
}
}

View file

@ -0,0 +1,252 @@
// explosion/knockback code
enum EDoExplosionFlags
{
DE_BLAST = 1, // sets BLASTED flag on pushed actors
DE_NOBLEED = 2, // does not spawn blood decals on hit
DE_NOSPLASH = 4, // like XF_NOSPLASH
DE_THRUWALLS = 8, // damages through geometry (no sight check)
DE_NOTMISSILE = 16, // instigator is the source itself (normally it'd be its target pointer)
DE_EXTRAZTHRUST = 32, // applies a higher Z thrust to enemies on ground
DE_HOWL = 64, // 25% chance for hit enemies to howl
DE_COUNTENEMIES = 128, // only count hits for hostiles
DE_COUNTSTEALTH = 256, // only count hits for inactive monsters
DE_COUNTFHKILLS = 512, // only count kills for enemies that were at full health
DE_NOHURTFRIEND = 1024, // splash damage will not affect allies
DE_CENTERHEIGHT = 2048, // origin of explosion is at the center height of the source actor, rather than its base
DE_NONEXPLOSIVE = 4096, // does not count as explosive damage (DMG_EXPLOSION is not passed, and no blast taunts are used)
DE_QUADRAVOL = 8192 // splash burn from a Quadravol projectile, so it'll ignite enemies instead of dealing damage
};
extend Class SWWMUtility
{
// Apply full 3D knockback in a specific direction, useful for hitscan
static play void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer, bool ExtraZThrust = false )
{
if ( !Victim )
return;
if ( Victim.bDORMANT ) // no dormant knockback
return;
if ( !Victim.bSHOOTABLE && !Victim.bVULNERABLE )
return;
if ( Victim.bDONTTHRUST || (Victim.Mass >= Actor.LARGE_MASS) )
return;
Vector3 Momentum = HitDirection*MomentumTransfer;
if ( (Victim.pos.z <= Victim.floorz) || !Victim.TestMobjZ() )
Momentum.z = max(Momentum.z,(ExtraZThrust?.4:.1)*Momentum.length());
Momentum /= GameTicRate*max(50,Victim.Mass);
Victim.vel += Momentum;
}
// complete spherical and more accurate replacement of A_Explode
// 100% free of the buggery GZDoom's own splash damage has
// returns the number of shootables hit/killed
static play int, int DoExplosion( Actor Source, double Damage, double MomentumTransfer, double ExplosionRadius, double FullDamageRadius = 0., int flags = 0, Name DamageType = '', Actor ignoreme = null, int dmgflags = 0, Actor realsource = null, Actor realinflictor = null )
{
FullDamageRadius = min(FullDamageRadius,ExplosionRadius);
// debug, display radius sphere
if ( swwm_debugblast )
{
let s = Actor.Spawn("RadiusDebugSphere",(flags&DE_CENTERHEIGHT)?Source.Vec3Offset(0,0,Source.height/2):Source.pos);
s.Scale *= ExplosionRadius;
s.SetShade((Damage>0)?"Green":"Blue");
if ( FullDamageRadius > 0. )
{
let s = Actor.Spawn("RadiusDebugSphere",(flags&DE_CENTERHEIGHT)?Source.Vec3Offset(0,0,Source.height/2):Source.pos);
s.Scale *= FullDamageRadius;
s.SetShade("Red");
}
}
if ( !(flags&DE_NOSPLASH) ) Source.CheckSplash(ExplosionRadius);
double brange;
// sanity checks are needed to avoid division by zero or other weirdness
if ( ExplosionRadius == FullDamageRadius ) brange = 1.;
else brange = 1./(ExplosionRadius-FullDamageRadius);
Actor Instigator = realsource?realsource:(flags&DE_NOTMISSILE)?Source:Source.target;
int dflg = ((flags&DE_NONEXPLOSIVE)?0:DMG_EXPLOSION)|dmgflags;
BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius);
int nhit = 0, nkill = 0;
bool haskilled = false;
Array<Actor> washit;
washit.Clear();
while ( bi.Next() )
{
Actor a = bi.Thing;
washit.Push(a);
// early checks for self and ignored actor (usually the instigator)
if ( !a || (a == ignoreme) || (a == Source) )
continue;
// can't be affected
if ( !a.bSHOOTABLE && !a.bVULNERABLE )
continue;
// no blasting if no radius dmg (unless forced)
if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG )
continue;
// check the DONTHARMCLASS/DONTHARMSPECIES flags
if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) )
continue;
// check friendliness
if ( (flags&DE_NOHURTFRIEND) && Instigator && Instigator.IsFriend(a) )
continue;
// can we see it
if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
// intersecting?
if ( !SphereIntersect(a,Source.pos,ExplosionRadius) )
continue;
// calculate factor
Vector3 dir;
if ( flags&DE_CENTERHEIGHT ) dir = level.Vec3Diff(Source.Vec3Offset(0,0,Source.Height/2),a.Vec3Offset(0,0,a.Height/2));
else dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2));
double dist = dir.length();
// intersecting, randomize direction
if ( dir.length() <= double.epsilon )
{
double ang = FRandom[DoBlast](0,360);
double pt = FRandom[DoBlast](-90,90);
dir = Vec3FromAngles(ang,pt);
}
dir /= dist;
dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius));
double damagescale;
if ( ExplosionRadius == FullDamageRadius ) damagescale = 1.;
else damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.);
double mm = MomentumTransfer*damagescale;
// no knockback if massive/unpushable
if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) )
{
Vector3 Momentum = dir*mm;
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length());
Momentum /= GameTicRate*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed
a.vel += Momentum;
if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true;
}
// hit it
bool inactive = (!a.player&&!a.target);
bool hostile = (Instigator&&a.IsHostile(Instigator)&&(a.bISMONSTER||a.player));
if ( (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nhit++;
int dmg = int(Damage*damagescale);
if ( flags&DE_QUADRAVOL )
{
OnFire.Apply(a,Instigator,dmg); // ignite
continue;
}
if ( dmg <= 0 ) continue; // no harm
int oldhp = a.Health;
int basehp = a.GetSpawnHealth();
int ndmg = a.DamageMobj(realinflictor?realinflictor:Source,Instigator,dmg,(DamageType=='')?Source.DamageType:DamageType,dflg,atan2(-dir.y,-dir.x));
if ( (ndmg > 0) && a && !(flags&DE_NOBLEED) ) a.TraceBleed(ndmg,Source);
if ( (flags&DE_HOWL) && a && (a.Health > 0) && a.bISMONSTER && !Random[DoBlast](0,3) ) a.Howl();
if ( hostile && (!a || (a.Health <= 0)) ) haskilled = true;
if ( (flags&DE_COUNTFHKILLS) && (oldhp < basehp) ) continue; // was not at full health
if ( (!a || (a.Health <= 0)) && (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nkill++;
}
// traverse portals (needed since BlockThingsIterator can't properly cross sector portals in both vertical directions)
int thisgroup = Source.CurSector.portalgroup;
for ( int i=0; i<level.GetPortalGroupCount(); i++ )
{
if ( i == thisgroup ) continue;
Vector2 relpos = Source.pos.xy+level.GetDisplacement(thisgroup,i);
bi = BlockThingsIterator.CreateFromPos(relpos.x,relpos.y,Source.pos.z,Source.pos.z+Source.height,ExplosionRadius,false);
while ( bi.Next() )
{
Actor a = bi.Thing;
// early exit for already processed actors
if ( washit.Find(a) < washit.Size() )
continue;
washit.Push(a);
// early checks for self and ignored actor (usually the instigator)
if ( !a || (a == ignoreme) || (a == Source) )
continue;
// can't be affected
if ( !a.bSHOOTABLE && !a.bVULNERABLE )
continue;
// no blasting if no radius dmg (unless forced)
if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG )
continue;
// check the DONTHARMCLASS/DONTHARMSPECIES flags
if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) )
continue;
// check friendliness
if ( (flags&DE_NOHURTFRIEND) && Instigator && Instigator.IsFriend(a) )
continue;
// can we see it
if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
// intersecting?
if ( !SphereIntersect(a,Source.pos,ExplosionRadius) )
continue;
// calculate factor
Vector3 dir;
if ( flags&DE_CENTERHEIGHT ) dir = level.Vec3Diff(Source.Vec3Offset(0,0,Source.Height/2),a.Vec3Offset(0,0,a.Height/2));
else dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2));
double dist = dir.length();
// intersecting, randomize direction
if ( dir.length() <= double.epsilon )
{
double ang = FRandom[DoBlast](0,360);
double pt = FRandom[DoBlast](-90,90);
dir = Vec3FromAngles(ang,pt);
}
dir /= dist;
dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius));
double damagescale;
if ( ExplosionRadius == FullDamageRadius ) damagescale = 1.;
else damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.);
double mm = MomentumTransfer*damagescale;
// no knockback if massive/unpushable
if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) )
{
Vector3 Momentum = dir*mm;
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length());
Momentum /= GameTicRate*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed
a.vel += Momentum;
if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true;
}
// hit it
bool inactive = (!a.player&&!a.target);
bool hostile = (Instigator&&a.IsHostile(Instigator)&&(a.bISMONSTER||a.player));
if ( (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nhit++;
int dmg = int(Damage*damagescale);
if ( flags&DE_QUADRAVOL )
{
OnFire.Apply(a,Instigator,dmg); // ignite
continue;
}
if ( dmg <= 0 ) continue; // no harm
int oldhp = a.Health;
int basehp = a.GetSpawnHealth();
int ndmg = a.DamageMobj(realinflictor?realinflictor:Source,Instigator,dmg,(DamageType=='')?Source.DamageType:DamageType,dflg,atan2(-dir.y,-dir.x));
if ( (ndmg > 0) && a && !(flags&DE_NOBLEED) ) a.TraceBleed(ndmg,Source);
if ( (flags&DE_HOWL) && a && (a.Health > 0) && a.bISMONSTER && !Random[DoBlast](0,3) ) a.Howl();
if ( hostile && (!a || (a.Health <= 0)) ) haskilled = true;
if ( (flags&DE_COUNTFHKILLS) && (oldhp < basehp) ) continue; // was not at full health
if ( (!a || (a.Health <= 0)) && (!(flags&DE_COUNTENEMIES) || hostile) && (!(flags&DE_COUNTSTEALTH) || inactive) ) nkill++;
}
}
if ( (Instigator is 'Demolitionist') && haskilled && !(flags&DE_NONEXPLOSIVE) )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
let demo = Demolitionist(Instigator);
if ( hnd && (gametic > demo.lastbang+30) && (gametic > hnd.lastcombat+10) && !Random[DemoLines](0,1) )
demo.lastbang = SWWMHandler.AddOneLiner("blast",2,10);
}
return nhit, nkill;
}
}
Class RadiusDebugSphere : SWWMNonInteractiveActor
{
Default
{
RenderStyle "AddStencil";
StencilColor "White";
}
States
{
Spawn:
XZW1 A 1 BRIGHT A_FadeOut();
Wait;
}
}

View file

@ -0,0 +1,153 @@
// compat / detection code
extend Class SWWMUtility
{
// try to be as thorough as possible in checking if DEHACKED has altered this actor
static clearscope bool CheckDehackery( Class<Actor> cls )
{
let def = GetDefaultByType(cls);
for ( State s=def.SpawnState; s; s=s.NextState )
{
if ( s.bDEHACKED ) return true;
// keep checking until we hit a loop, just in case
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
}
for ( State s=def.SeeState; s; s=s.NextState )
{
if ( s.bDEHACKED ) return true;
// keep checking until we hit a loop, just in case
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
}
for ( State s=def.MissileState; s; s=s.NextState )
{
if ( s.bDEHACKED ) return true;
// keep checking until we hit a loop, just in case
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
}
for ( State s=def.MeleeState; s; s=s.NextState )
{
if ( s.bDEHACKED ) return true;
// keep checking until we hit a loop, just in case
if ( s.NextState && (s.DistanceTo(s.NextState) <= 0) ) break;
}
return false;
}
// shorthand for some of these checks (these are generally used by the mission briefing system)
static bool IsKnownMap()
{
if ( (gameinfo.gametype&GAME_DOOM) && (IsKnownCustomWAD() || CheckMD5List("vanilladoom.lst")) )
return true;
if ( (gameinfo.gametype&GAME_HERETIC) && CheckMD5List("vanillaheretic.lst") )
return true;
if ( (gameinfo.gametype&GAME_HEXEN) && CheckMD5List("vanillahexen.lst") )
return true;
return false;
}
// to be filled
static bool IsKnownCustomWAD()
{
if ( gameinfo.gametype&GAME_DOOM )
{
if ( IsEviternity() ) return true;
if ( IsUltDoom2() ) return true;
}
return false;
}
// detect ultimate doom 2
static bool IsUltDoom2()
{
return CheckMD5List("ultdoom2.lst");
}
// detect eviternity (naive method)
static bool IsEviternity()
{
foreach ( cls:AllActorClasses )
{
if ( cls.GetClassName() != "Archangelus" )
continue;
return true;
}
return false;
}
// detect doom vacation
static bool InDoomVacation()
{
// cheap, but hey, it should work
if ( Wads.FindLump("VACABEX") != -1 )
{
// just to make sure
foreach ( cls:AllActorClasses )
{
if ( cls.GetClassName() != "Babe" )
continue;
return true;
}
}
return false;
}
static bool IsDeathkings()
{
if ( (gameinfo.gametype&GAME_HEXEN) && LevelInfo.MapExists("MAP41") )
{
let inf = LevelInfo.FindLevelInfo("MAP41");
if ( inf.cluster == 1 ) return true;
}
return false;
}
// detect vanilla maps (across all IWAD versions)
static bool CheckMD5List( String fname )
{
String csum = level.GetChecksum();
let lmp = Wads.CheckNumForFullName(fname);
if ( lmp == -1 ) return false;
String dat = Wads.ReadLump(lmp);
// fucking Windows
dat.Replace("\r","");
Array<String> list;
list.Clear();
dat.Split(list,"\n");
foreach ( l:list )
{
if ( (l.Length() == 0) || (l.Left(1) == "#") || (l.Left(1) == "") )
continue;
if ( csum ~== l )
return true;
}
return false;
}
// WHACK
static play void EndLevelDie( Actor victim )
{
victim.DamageMobj(null,null,victim.Health,'EndLevel',DMG_FORCED|DMG_THRUSTLESS);
}
// for Equinox
static play void SpawnVanillaBossBrain( int tid )
{
let ai = Level.CreateActorIterator(tid);
Actor a;
while ( a = ai.Next() )
{
let bb = a.Spawn("BossBrain",a.pos,NO_REPLACE);
bb.angle = a.angle;
}
}
// checks if we're playing in doom 1
// this is used so we can sometimes replace the shotgun with a SSG slot weapon
static bool IsDoomOne()
{
if ( !(gameinfo.GameType&GAME_DOOM) ) return false;
// is the map in ExMx format? Then it's likely we're playing a doom 1 map
if ( (level.mapname.Length() >= 4) && (level.mapname.Mid(0,1) == "E") && (level.mapname.ByteAt(1) >= 0x30) && (level.mapname.ByteAt(1) < 0x40) && (level.mapname.Mid(2,1) == "M") && (level.mapname.ByteAt(3) >= 0x30) && (level.mapname.ByteAt(3) < 0x40) )
return true;
return false;
}
}

View file

@ -0,0 +1,440 @@
// identification and tagging
extend Class SWWMUtility
{
// because GetTag() returns the localized string, we need to do things the hard way
static play String GetFunTag( SWWMHandler hnd, Actor a, String defstr = "" )
{
// look up fun tag services if available
foreach ( sv:hnd.funtagsv )
{
if ( !sv ) continue;
String res = sv.GetString("GetFunTag",objectArg:a);
if ( res == "" ) continue;
return res;
}
int ntags = 1;
String basetag = "";
switch ( a.GetClassName() )
{
// Doom
case 'ZombieMan':
case 'StealthZombieMan':
basetag = "ZOMBIE";
break;
case 'ShotgunGuy':
case 'StealthShotgunGuy':
basetag = "SHOTGUN";
break;
case 'ChaingunGuy':
case 'StealthChaingunGuy':
basetag = "HEAVY";
break;
case 'DoomImp':
case 'StealthDoomImp':
basetag = "IMP";
break;
case 'Demon':
case 'StealthDemon':
basetag = "DEMON";
break;
case 'Spectre':
basetag = "SPECTRE";
break;
case 'LostSoul':
basetag = "LOST";
break;
case 'Cacodemon':
case 'StealthCacodemon':
basetag = "CACO";
break;
case 'HellKnight':
case 'StealthHellKnight':
basetag = "HELL";
break;
case 'BaronOfHell':
case 'StealthBaron':
basetag = "BARON";
break;
case 'Arachnotron':
case 'StealthArachnotron':
basetag = "ARACH";
break;
case 'PainElemental':
basetag = "PAIN";
break;
case 'Revenant':
case 'StealthRevenant':
basetag = "REVEN";
break;
case 'Fatso':
case 'StealthFatso':
basetag = "MANCU";
break;
case 'Archvile':
case 'StealthArchvile':
basetag = "ARCH";
break;
case 'SpiderMastermind':
basetag = "SPIDER";
break;
case 'Cyberdemon':
basetag = "CYBER";
break;
case 'SWWMBossBrain':
basetag = "BOSSBRAIN";
break;
case 'WolfensteinSS':
if ( IsUltDoom2() )
{
basetag = "ELITEZOMBIE";
break;
}
// ensure it's not being replaced
if ( CheckDehackery('WolfensteinSS') ) break;
case 'SWWMSS':
basetag = "WOLFSS";
break;
case 'SWWMHangingKeen':
basetag = "KEEN";
break;
case 'MBFHelperDog':
case 'SWWMDog':
basetag = "DOG";
break;
case 'SWWMGuard':
basetag = "WOLFGUARD";
break;
case 'SWWMHans':
basetag = "WOLFHANS";
break;
// Heretic
case 'Chicken':
basetag = "CHICKEN";
break;
case 'Beast':
basetag = "BEAST";
break;
case 'Clink':
basetag = "CLINK";
break;
case 'Sorcerer1':
case 'Sorcerer2':
basetag = "DSPARIL";
break;
case 'HereticImp':
case 'HereticImpLeader':
basetag = "HERETICIMP";
break;
case 'Ironlich':
basetag = "IRONLICH";
break;
case 'Knight':
case 'KnightGhost':
basetag = "BONEKNIGHT";
break;
case 'Minotaur':
case 'MinotaurFriend':
basetag = "MINOTAUR";
break;
case 'Mummy':
case 'MummyGhost':
basetag = "MUMMY";
break;
case 'MummyLeader':
case 'MummyLeaderGhost':
basetag = "MUMMYLEADER";
break;
case 'Snake':
basetag = "SNAKE";
break;
case 'Wizard':
basetag = "WIZARD";
break;
// Hexen
case 'FireDemon':
basetag = "FIREDEMON";
break;
case 'Demon1':
case 'Demon1Mash':
case 'Demon2':
case 'Demon2Mash':
basetag = "DEMON1";
break;
case 'Ettin':
case 'EttinMash':
basetag = "ETTIN";
break;
case 'Centaur':
case 'CentaurMash':
basetag = "CENTAUR";
break;
case 'CentaurLeader':
basetag = "SLAUGHTAUR";
break;
case 'Bishop':
basetag = "BISHOP";
break;
case 'IceGuy':
basetag = "ICEGUY";
break;
case 'Serpent':
case 'SerpentLeader':
basetag = "SERPENT";
break;
case 'Wraith':
case 'WraithBuried':
basetag = "WRAITH";
break;
case 'Dragon':
basetag = "DRAGON";
break;
case 'Korax':
basetag = "KORAX";
break;
case 'FighterBoss':
basetag = "FBOSS";
break;
case 'MageBoss':
basetag = "MBOSS";
break;
case 'ClericBoss':
basetag = "CBOSS";
break;
case 'Heresiarch':
basetag = "HERESIARCH";
break;
case 'Pig':
basetag = "PIG";
break;
// eviternity
case 'ArchangelusA':
case 'ArchangelusB':
basetag = "ANGEL";
break;
case 'AstralCaco':
basetag = "ASTRAL";
break;
case 'Annihilator':
basetag = "ANNIHIL";
break;
case 'FormerCaptain':
basetag = "FCAPTAIN";
break;
case 'NightmareDemon':
basetag = "NDEMON";
break;
}
if ( basetag == "" ) return a.GetTag(defstr);
String funtag = "FN_"..basetag.."_FUN";
String lfuntag = StringTable.Localize(funtag,false);
if ( lfuntag != funtag ) return lfuntag;
String nfuntag = "FN_"..basetag.."_FUNN";
String lnfuntag = StringTable.Localize(nfuntag,false);
if ( lnfuntag == nfuntag ) return a.GetTag(defstr);
ntags = lnfuntag.ToInt();
return StringTable.Localize(String.Format("$FN_%s_FUN%d",basetag,Random[FunTags](1,ntags)));
}
// used to "substitute" a monster class for another so killcount stats are merged
// e.g.: "stealth" monsters and their non-stealth counterparts,
// or "HereticImp" and "HereticImpLeader", which have the same exact tag,
// and would result in an odd "duplication" of monster names
static play Class<Actor> MergeMonster( SWWMHandler hnd, Class<Actor> a )
{
// see if any services can resolve this first
foreach ( sv:hnd.mergemonstersv )
{
if ( !sv ) continue;
String res = sv.GetString("MergeMonster",stringArg:a.GetClassName());
if ( res == "" ) continue;
Class<Actor> rescls = res;
return rescls;
}
// stealth monsters, the worst thing ever invented
if ( a == 'StealthArachnotron' ) return 'Arachnotron';
if ( a == 'StealthArchvile' ) return 'Archvile';
if ( a == 'StealthBaron' ) return 'BaronOfHell';
if ( a == 'StealthCacodemon' ) return 'Cacodemon';
if ( a == 'StealthChaingunGuy' ) return 'ChaingunGuy';
if ( a == 'StealthDemon' ) return 'Demon';
if ( a == 'StealthHellKnight' ) return 'HellKnight';
if ( a == 'StealthDoomImp' ) return 'DoomImp';
if ( a == 'StealthFatso' ) return 'Fatso';
if ( a == 'StealthRevenant' ) return 'Revenant';
if ( a == 'StealthShotgunGuy' ) return 'ShotgunGuy';
if ( a == 'StealthZombieMan' ) return 'ZombieMan';
// heretic monsters
if ( a == 'Sorcerer2' ) return 'Sorcerer1';
if ( a == 'HereticImpLeader' ) return 'HereticImp';
if ( a == 'KnightGhost' ) return 'Knight';
if ( a == 'MummyGhost' ) return 'Mummy';
if ( a == 'MummyLeaderGhost' ) return 'MummyLeader';
// hexen monsters
if ( a == 'CentaurMash' ) return 'Centaur';
if ( a == 'Demon1Mash' ) return 'Demon1';
if ( a == 'Demon2' ) return 'Demon1';
if ( a == 'Demon2Mash' ) return 'Demon1';
if ( a == 'EttinMash' ) return 'Ettin';
if ( a == 'SerpentLeader' ) return 'Serpent';
if ( a == 'WraithBuried' ) return 'Wraith';
return a;
}
// gendered languages are a fuck
static clearscope bool SellFemaleItem( Inventory i, String loc = "SWWM_SELLEXTRA_FEM" )
{
// no gendered string alt
if ( StringTable.Localize("$"..loc) == loc )
return false;
if ( i is 'DeepImpact' ) return true;
if ( i is 'ExplodiumGun' ) return true;
if ( i is 'Wallbuster' ) return true;
if ( i is 'HeavyMahSheenGun' ) return true;
if ( i is 'Quadravol' ) return true;
if ( i is 'Sparkster' ) return true;
if ( i is 'CandyGun' ) return true;
if ( i is 'RayKhom' ) return true;
if ( i is 'RafanKos' ) return true;
if ( i is 'HealthNuggetItem' ) return true;
if ( i is 'ArmorNuggetItem' ) return true;
if ( i is 'WarArmor' ) return true;
if ( i is 'FuckingInvinciball' ) return true;
if ( i is 'SWWMLamp' ) return true;
if ( i is 'AngerySigil' ) return true;
return false;
}
// returns the plural tag (if available)
static clearscope string GetAmmoTag( Inventory i )
{
if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..MagAmmo(i).PickupTag.."S");
if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..SWWMAmmo(i).PickupTag.."S");
return i.GetTag();
}
// because of zscript fuckery with GetDefaultByType
static clearscope string GetAmmoTagClass( Class<Inventory> i )
{
if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class<MagAmmo>)(i)).PickupTag.."S");
if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class<SWWMAmmo>)(i)).PickupTag.."S");
return GetDefaultByType(i).GetTag();
}
// IsZeroDamage() can lead to some false negatives, we have to account for that
static play bool ValidProjectile( Actor a )
{
if ( !a.bMISSILE ) return false;
if ( a is 'AirBullet' ) return true;
if ( a is 'ExplodiumMagProj' ) return true;
if ( a is 'TheBall' ) return true;
if ( a is 'EvisceratorChunk' ) return true;
if ( a is 'EvisceratorProj' ) return true;
if ( a is 'HellblazerMissile' ) return true;
if ( a is 'QuadProj' ) return true;
if ( a is 'BigBiospark' ) return true;
if ( a is 'BiosparkBall' ) return true;
if ( a is 'BiosparkCore' ) return true;
if ( a is 'CandyGunProj' ) return true;
if ( a is 'CandyMagProj' ) return true;
if ( a is 'MisterGrenade' ) return true;
if ( a is 'LoveHeart' ) return true;
if ( !a.IsZeroDamage() ) return true;
return false;
}
// Is this a beam projectile? (speed == length)
static play bool IsBeamProj( Actor a )
{
if ( a is 'SaltBeam' ) return true;
if ( a is 'BiosparkBeam' ) return true;
if ( a is 'BiosparkArc' ) return true;
if ( a is 'CandyBeam' ) return true;
if ( a is 'YnykronBeam' ) return true;
if ( a is 'YnykronLightningArc' ) return true;
if ( a is 'YnykronAltBeam' ) return true;
if ( a is 'MykradvoTendril' ) return true;
if ( a is 'MisterRailBeam' ) return true;
return false;
}
// is this a YBeam type? (real pitch is pitch-90)
static play bool IsYBeam( Actor a )
{
if ( a is 'MisterRailBeam' ) return true;
return false;
}
static clearscope bool IdentifyingDog( Actor a )
{
if ( a is 'MBFHelperDog' ) return true;
if ( a is 'SWWMDog' ) return true;
if ( a.GetClassName() == 'GermanDog' ) return true; // brote dote
if ( a.GetClassName() == '64HellHound' ) return true; // brote dote 64
if ( a.GetClassName() == 'AbyssDemon2' ) return true; // CH
if ( a.GetClassName() == 'WHOLETTHEDOGSOUT' ) return true; // CH
// more dogs will be added as found
// because all dogs must be pet
return false;
}
static clearscope bool IdentifyingCaco( Actor a )
{
if ( a is 'DeadCacodemon' ) return false;
if ( a is 'Cacodemon' ) return true;
if ( a.Species == 'RLCacodemon' ) return true; // DRLA
if ( a.Species == 'Caco' ) return true; // CH and others
if ( a.Species == 'Cacodemon' ) return true; // Beautiful Doom
if ( a.GetClassName() == 'AstralCaco' ) return true; // Eviternity
if ( a.GetParentClass().GetClassName() == 'LEG_BaseCaco' ) return true; // LEGION
return false;
}
// Друг
static clearscope bool IdentifyingDrug( Actor a )
{
if ( a is 'Beast' ) return true;
return false;
}
static clearscope bool IdentifyingDoubleBoi( Actor a )
{
if ( a is 'Ettin' ) return true;
return false;
}
static bool IsVipItem( Actor target )
{
if ( (target is 'Chancebox') && (target.CurState==target.SpawnState) )
return true;
if ( target is 'SWWMCollectible' )
return true;
if ( (target is 'Ynykron') || (target is 'RafanKos') )
return true;
if ( (target is 'GoldShell') || (target is 'YnykronAmmo') || (target is 'UltimatePod') || (target is 'UltimateAmmo') )
return true;
if ( (target is 'Mykradvo') || (target is 'AngerySigil') || (target is 'DivineSprite') )
return true;
if ( target is 'PuzzleItem' )
return true;
return false;
}
// used by the store
static bool IsVipItemClass( Class<Actor> target )
{
if ( (target is 'Ynykron') || (target is 'RafanKos') )
return true;
if ( (target is 'GoldShell') || (target is 'YnykronAmmo') || (target is 'UltimatePod') || (target is 'UltimateAmmo') )
return true;
if ( (target is 'Mykradvo') || (target is 'AngerySigil') || (target is 'DivineSprite') )
return true;
return false;
}
static bool IsScoreItem( Actor target )
{
if ( target is 'Key' )
return true;
if ( target is 'HammerspaceEmbiggener' )
return true;
return target.bCOUNTITEM;
}
}

View file

@ -0,0 +1,327 @@
// inventory-related functions
extend Class SWWMUtility
{
// full reset of inventory (excluding collectibles, and optionally resetting the score)
static play void WipeInventory( Actor mo, bool resetscore = false, bool allplayers = false )
{
if ( allplayers )
{
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
WipeInventory(players[i].mo,resetscore,false);
}
return;
}
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
SWWMCredits c = SWWMCredits.Find(p);
if ( resetscore && c ) c.credits = 0;
Actor last = p.mo;
while ( last.inv )
{
let inv = last.inv;
if ( !(inv is 'SWWMCollectible') )
{
inv.Destroy();
if ( !inv.bDestroyed ) last = inv;
}
else last = inv;
}
p.mo.GiveDefaultInventory();
p.mo.BringUpWeapon();
p.health = p.mo.Health = p.mo.SpawnHealth();
}
// sets all carried ammo back to zero
// resets hammerspace capacity
static play void ResetAmmo( Actor mo )
{
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
for ( Inventory i=p.mo.inv; i; i=i.inv )
{
if ( (i is 'Ammo') || (i is 'MagAmmo') )
{
i.Amount = 0;
i.MaxAmount = i.default.MaxAmount;
}
if ( i is 'HammerspaceEmbiggener' )
i.Amount = 0;
}
// also gives back any ammo from carried weapons (provided they don't have NOFIRSTGIVE)
for ( Inventory i=p.mo.inv; i; i=i.inv )
{
if ( !(i is 'Weapon') ) continue;
let w = Weapon(i);
if ( (w is 'SWWMWeapon') && SWWMWeapon(w).bNOFIRSTGIVE ) continue;
if ( w.Ammo1 ) w.Ammo1.Amount = min(w.Ammo1.MaxAmount,w.Ammo1.Amount+w.default.AmmoGive1);
if ( w.Ammo2 && (w.Ammo2 != w.Ammo1) ) w.Ammo2.Amount = min(w.Ammo2.MaxAmount,w.Ammo2.Amount+w.default.AmmoGive2);
}
}
// removes all usable items
static play void ResetItems( Actor mo )
{
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
Actor last = p.mo;
while ( last.inv )
{
let inv = last.inv;
if ( !inv.bINVBAR )
{
last = inv;
continue;
}
inv.Destroy();
if ( !inv.bDestroyed ) last = inv;
}
}
// resets health and removes worn armor
static play void ResetHealth( Actor mo )
{
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
p.health = p.mo.health = 100;
for ( Inventory i=p.mo.inv; i; i=i.inv )
{
if ( !(i is 'SWWMArmor') ) continue;
i.Amount = 0;
}
}
// what RandomSpawner does, basically (simplified for items)
static play void TransferItemProp( Actor a, Actor b, bool bundlehack = false )
{
if ( bundlehack )
{
b.spawnpoint = b.pos;
b.spawnangle = int(b.angle);
}
else
{
b.spawnpoint = a.spawnpoint;
b.spawnangle = a.spawnangle;
b.angle = a.angle;
b.pitch = a.pitch;
b.roll = a.roll;
}
b.special = a.special;
b.FloatBobPhase = a.FloatBobPhase; // important
for ( int i=0; i<5; i++ ) b.args[i] = a.args[i];
b.special1 = a.special1;
b.special2 = a.special2;
b.spawnflags = a.spawnflags&~MTF_SECRET;
b.HandleSpawnFlags();
b.spawnflags = a.spawnflags;
b.bCountSecret = a.spawnflags&MTF_SECRET;
b.ChangeTid(a.tid);
b.vel = b.vel;
b.master = b.master;
b.tracer = b.tracer;
b.target = b.target;
b.bDROPPED = a.bDROPPED;
}
// check that all players can get enough of this if needed
// multi: check for multiple copies, not just single instances
// (useful e.g. for dual wieldable weapons)
static bool CheckNeedsItem( Class<Inventory> itm, bool multi = false )
{
int np = 0;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
np++;
}
int required = np;
if ( multi ) required *= GetDefaultByType(itm).MaxAmount;
// subtract all that exist already (either in world or owned)
let ti = ThinkerIterator.Create(itm);
Inventory i;
while ( i = Inventory(ti.Next()) )
{
if ( multi ) required -= i.Amount;
else required--;
}
// check travelling inventory separately, as by default iterators don't check anything below STAT_FIRST_THINKING
ti = ThinkerIterator.Create(itm,Thinker.STAT_TRAVELLING);
while ( i = Inventory(ti.Next()) )
{
if ( multi ) required -= i.Amount;
else required--;
}
return (required>0);
}
// checks if instances of a certain item exist
// skipme: optionally, ignore checking for one specific instance
// (useful to check if we're the only copy of an item)
// mapstart: this function is being called during map load, so we
// should also check STAT_TRAVELLING inventory
// worldonly: only checks for items that are placed in the world
// ownedonly: only checks for items that are owned by players
// (note that this is mutually exclusive with worldonly)
static bool ItemExists( Class<Inventory> itm, Inventory skipme = null, bool mapstart = false, bool worldonly = false, bool ownedonly = false )
{
let ti = ThinkerIterator.Create(itm);
Inventory i;
while ( i = Inventory(ti.Next()) )
{
if ( i == skipme ) continue;
if ( worldonly && i.Owner ) continue;
if ( ownedonly && (!i.Owner || !i.Owner.player) ) continue;
return true;
}
if ( worldonly || !mapstart ) return false;
ti = ThinkerIterator.Create(itm,Thinker.STAT_TRAVELLING);
while ( i = Inventory(ti.Next()) )
{
if ( i == skipme ) continue;
if ( ownedonly && (!i.Owner || !i.Owner.player) ) continue;
return true;
}
return false;
}
// multi-weapon spawn stuff
static private Class<Weapon> PickPair( Class<Weapon> a, Class<Weapon> b )
{
if ( !ItemExists(a,mapstart:true) ) return a;
if ( !ItemExists(b,mapstart:true) ) return b;
return Random[Replacements](0,1)?a:b;
}
// melee weapon + extra slot 2 guns
static Class<Weapon> PickSWWMSlot1()
{
// [GROSS HACK] default to a hammer if there are no players
// (this genuinely can happen, if player starts were placed AFTER the item)
int np = 0;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
np++;
}
if ( np <= 0 ) return 'ItamexHammer';
// so the player can recover it if they decided to drop it in a previous map, or they didn't start with it
if ( CheckNeedsItem('DeepImpact') ) return 'DeepImpact';
if ( CheckNeedsItem('ItamexHammer') ) return 'ItamexHammer';
return PickSWWMSlot2();
}
// pistol spawn, pretty simple
static Class<Weapon> PickSWWMSlot2()
{
// as they are dual-wieldable, there should be a 50% chance for spares to also appear if needed
if ( Random[Replacements](0,1) && !CheckNeedsItem('ExplodiumGun') && CheckNeedsItem('ExplodiumGun',true) )
return 'ExplodiumGun';
if ( Random[Replacements](0,1) && !CheckNeedsItem('PlasmaBlast') && CheckNeedsItem('PlasmaBlast',true) )
return 'PlasmaBlast';
return PickPair('ExplodiumGun','PlasmaBlast');
}
// shotgun spawn
static Class<Weapon> PickSWWMSlot3()
{
return PickPair('Spreadgun','PuntzerBeta');
}
// super shotgun spawn
static Class<Weapon> PickSWWMSlot4()
{
return PickPair('Wallbuster','PuntzerGamma');
}
// chaingun spawn
static Class<Weapon> PickSWWMSlot5()
{
return PickPair('Eviscerator','HeavyMahSheenGun');
}
// rocket launcher spawn
static Class<Weapon> PickSWWMSlot6()
{
return PickPair('Hellblazer','Quadravol');
}
// first plasma rifle spawn
static Class<Weapon> PickSWWMSlot7()
{
return PickPair('Sparkster','ModernSparkster');
}
// second plasma rifle spawn
static Class<Weapon> PickSWWMSlot8()
{
return PickPair('SilverBullet','RayKhom');
}
// first bfg spawn
static Class<Weapon> PickSWWMSlot9()
{
// 25% chance to still drop another candy gun if it's not at max capacity
if ( !Random[Replacements](0,3) && ItemExists('CandyGun') && CheckNeedsItem('CandyGunSpares',true) )
return 'CandyGun';
return PickPair('CandyGun','MisterRifle');
}
// second bfg spawn (each weapon can only exist once)
static Class<Weapon> PickSWWMSlot0( bool fallback = true )
{
if ( ItemExists('Ynykron',mapstart:true) )
{
if ( ItemExists('RafanKos',mapstart:true) )
return fallback?PickSWWMSlot9():null;
return 'RafanKos';
}
if ( ItemExists('RafanKos',mapstart:true) )
return 'Ynykron';
return Random[Replacements](0,1)?'Ynykron':'RafanKos';
}
// either plasma rifle spawn
static Class<Weapon> PickDoomSlot6()
{
bool hasslot7 = (!CheckNeedsItem('Sparkster')||!CheckNeedsItem('ModernSparkster'));
bool hasslot8 = (!CheckNeedsItem('SilverBullet')||!CheckNeedsItem('RayKhom'));
// if the player already has a slot 7 weapon...
if ( hasslot7 )
{
// ... and also has a slot 8 weapon, 33% chance of a slot 8 spawn
// otherwise, guaranteed slot 8 spawn
if ( hasslot8 && Random[Replacements](0,2) ) return PickSWWMSlot7();
else return PickSWWMSlot8();
}
// otherwise, always spawn a slot 7 weapon first
return PickSWWMSlot7();
}
// either bfg spawn
static Class<Weapon> PickDoomSlot7()
{
bool hasslot9 = (!CheckNeedsItem('CandyGun')||!CheckNeedsItem('MisterRifle'));
bool hasslot0 = (!CheckNeedsItem('Ynykron')||!CheckNeedsItem('RafanKos'));
let rep = PickSWWMSlot0(false);
// if the player already has a slot 9 weapon (and a slot 0 weapon can still spawn)...
if ( hasslot9 && rep )
{
// ... and also has a slot 0 weapon already, 33% chance of a slot 0 spawn
// otherwise, guaranteed slot 0 spawn
if ( hasslot0 && Random[Replacements](0,2) ) return PickSWWMSlot9();
else return rep;
}
// otherwise, always spawn a slot 9 weapon first
return PickSWWMSlot9();
}
// either shotgun spawn (also used for Heretic)
static Class<Weapon> PickDoomSlot3()
{
// always slot 3 after map start, prevents shotgun guys from dropping wallbusters, which is weird af
if ( level.maptime ) return PickSWWMSlot3();
bool hasslot3 = (!CheckNeedsItem('Spreadgun')||!CheckNeedsItem('PuntzerBeta'));
bool hasslot4 = (!CheckNeedsItem('Wallbuster')||!CheckNeedsItem('PuntzerGamma'));
// if the player already has a slot 3 weapon...
if ( hasslot3 )
{
// ... and also has a slot 4 weapon, 33% chance of a slot 4 spawn
// otherwise, guaranteed slot 4 spawn
if ( hasslot4 && Random[Replacements](0,2) ) return PickSWWMSlot3();
return PickSWWMSlot4();
}
// otherwise, always spawn a slot 3 weapon first
return PickSWWMSlot3();
}
}

View file

@ -0,0 +1,622 @@
// map interaction/info functions
enum EExitType
{
ET_Normal,
ET_Secret,
ET_EndGame,
ET_NewMap,
};
extend Class SWWMUtility
{
// how the fuck is this not available to ZScript?
// copied from P_PointOnLineSidePrecise()
deprecated("4.11", "Use Level.PointOnLineSide() instead") static clearscope int PointOnLineSide( Vector2 p, Line l )
{
if ( !l ) return 0;
return (((p.y-l.v1.p.y)*l.delta.x+(l.v1.p.x-p.x)*l.delta.y) > double.epsilon);
}
// haha another one
// copied from BoxOnLineSide()
deprecated("4.11", "Use Level.BoxOnLineSide() instead") static clearscope int BoxOnLineSide( double top, double bottom, double left, double right, Line l )
{
if ( !l ) return 0;
int p1, p2;
if ( l.delta.x == 0 )
{
// ST_VERTICAL:
p1 = (right < l.v1.p.x);
p2 = (left < l.v1.p.x);
if ( l.delta.y < 0 )
{
p1 ^= 1;
p2 ^= 1;
}
}
else if ( l.delta.y == 0 )
{
// ST_HORIZONTAL:
p1 = (top > l.v1.p.y);
p2 = (bottom > l.v1.p.y);
if ( l.delta.x < 0 )
{
p1 ^= 1;
p2 ^= 1;
}
}
else if ( (l.delta.x*l.delta.y) >= 0 )
{
// ST_POSITIVE:
p1 = PointOnLineSide((left,top),l);
p2 = PointOnLineSide((right,bottom),l);
}
else
{
// ST_NEGATIVE:
p1 = PointOnLineSide((right,top),l);
p2 = PointOnLineSide((left,bottom),l);
}
return (p1==p2)?p1:-1;
}
// wrapper
deprecated("4.11", "Use Level.ActorOnLineSide() instead") static clearscope int ActorOnLineSide( Actor a, Line l )
{
double box[4];
box[0] = a.pos.y+a.radius;
box[1] = a.pos.y-a.radius;
box[2] = a.pos.x-a.radius;
box[3] = a.pos.x+a.radius;
return BoxOnLineSide(box[0],box[1],box[2],box[3],l);
}
// Thanks to ZZYZX and Nash
static play void SetToSlopeSpecific( Actor a, double dang, SecPlane plane, bool flipnorm )
{
Vector3 fnormal;
if ( flipnorm ) fnormal = -plane.Normal;
else fnormal = plane.Normal;
vector2 fnormalp1 = ((fnormal.x != 0) || (fnormal.y != 0))?(fnormal.x,fnormal.y).Unit():(0,0);
vector2 fnormalp2 = ((fnormal.x,fnormal.y).Length(),fnormal.z);
double fang = atan2(fnormalp1.y,fnormalp1.x); // floor angle (not pitch!)
double fpitch = atan2(fnormalp2.x,fnormalp2.y); // floor pitch
double ddiff1 = sin(fang-dang);
double ddiff2 = cos(fang-dang);
a.pitch = fpitch*ddiff2;
a.roll = -fpitch*ddiff1;
a.angle = dang;
}
static play void SetToSlope( Actor a, double dang, bool ceil = false )
{
Sector sect;
SecPlane plane;
Vector3 fnormal;
bool flipnorm;
if ( ceil )
{
sect = a.CeilingSector;
plane = sect.ceilingplane;
flipnorm = true;
fnormal = -sect.ceilingplane.Normal;
}
else
{
sect = a.FloorSector;
plane = sect.floorplane;
flipnorm = false;
fnormal = sect.floorplane.Normal;
}
// find closest 3d floor for its normal
F3DFloor ff;
for ( int i=0; i<sect.Get3DFloorCount(); i++ )
{
if ( !(sect.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !ceil && !(sect.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
else if ( ceil && !(sect.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.ceilingz) ) continue;
ff = sect.Get3DFloor(i);
break;
}
if ( ff )
{
if ( ceil )
{
plane = ff.bottom;
flipnorm = false;
fnormal = ff.bottom.Normal;
}
else
{
plane = ff.top;
flipnorm = true;
fnormal = -ff.top.Normal;
}
}
SetToSlopeSpecific(a,dang,plane,flipnorm);
}
static clearscope int GetLineLock( Line l )
{
int locknum = l.locknumber;
if ( !locknum )
{
// check the special
switch ( l.special )
{
case FS_Execute:
locknum = l.Args[2];
break;
case Door_LockedRaise:
case Door_Animated:
locknum = l.Args[3];
break;
case ACS_LockedExecute:
case ACS_LockedExecuteDoor:
case Generic_Door:
locknum = l.Args[4];
break;
}
}
return locknum;
}
// return if a line is an exit, and additionally the type of exit
static clearscope bool, int IsExitLine( Line l )
{
if ( l.special == Exit_Secret )
return true, ET_Secret;
if ( l.special == Exit_Normal )
return true, ET_Normal;
if ( l.special == Teleport_EndGame )
return true, ET_EndGame;
if ( l.special == Teleport_NewMap )
return true, ET_NewMap;
// E1M8 compat
if ( (l.special == ACS_Execute) && (l.Args[0] == -Int('E1M8_KNOCKOUT')) )
return true, ET_Normal;
// spooktober™
if ( ((l.special == ACS_Execute) || (l.special == ACS_ExecuteAlways)) && (l.Args[0] == -Int('MapFadeOut')) )
{
if ( level.levelnum == 1 )
{
let lv = levelinfo.FindLevelByNum(l.Args[2]);
if ( lv && lv.mapname.Left(6) ~== "SECRET" )
return true, ET_Secret;
else return true, ET_NewMap;
}
return true, ET_Normal;
}
return false, ET_Normal;
}
static clearscope bool IsTeleportLine( Line l, bool all = false )
{
// must be two-sided and crossable
if ( !l.sidedef[1] || !(l.Activation&(SPAC_Cross|SPAC_MCross|SPAC_PCross|SPAC_AnyCross)) ) return false;
// filter lines that aren't player-activated (unless checking all)
if ( !all && !(l.Activation&SPAC_PlayerActivate) ) return false;
// typical teleports
if ( (l.special == Teleport) || (l.special == Teleport_NoStop) )
return true;
// if checking all, also include sneaky teleports
if ( all && ((l.special == Teleport_Line) || (l.special == Teleport_NoFog)) )
return true;
// exits are included too
if ( IsExitLine(l) )
return true;
return false;
}
static clearscope bool IsValidLockNum( int l )
{
if ( (l < 1) || (l > 255) ) return true;
return SWWMCachedLockInfo.IsValidLock(l);
}
static clearscope Color GetLockColor( int l )
{
return SWWMCachedLockInfo.GetLockColor(l);
}
static clearscope bool IsDoorSector( Sector s, int part )
{
// super-easy mode: check for boss special sectors
if ( (level.mapname ~== "E1M8") || (level.mapname ~== "E2M8") || (level.mapname ~== "E3M8")
|| (level.mapname ~== "E4M6") || (level.mapname ~== "E4M8") || (level.mapname ~== "E5M8")
|| (level.mapname ~== "MAP07") )
{
let si = level.CreateSectorTagIterator(666);
int idx;
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
if ( level.mapname ~== "MAP07" )
{
let si2 = level.CreateSectorTagIterator(667);
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
}
}
// moderate: see if it's a busted crusher, we need to be able to break those in case they cause a softlock
let ti = ThinkerIterator.Create("SWWMCrusherBroken",Thinker.STAT_USER);
SWWMCrusherBroken cb;
while ( cb = SWWMCrusherBroken(ti.Next()) )
{
if ( (part == 0) && (cb.fsec == s) ) return true;
if ( (part == 1) && (cb.csec == s) ) return true;
}
// hard mode: look for all lines/actors with movement specials referencing us
foreach ( l:level.Lines )
{
if ( !l.special ) continue;
if ( (part && (l.special >= 10) && (l.special <= 13))
|| (!part && (l.special >= 20) && (l.special <= 25))
|| (!part && (l.special == 28))
|| ((l.special >= 29) && (l.special <= 30))
|| (!part && (l.special >= 35) && (l.special <= 37))
|| (part && (l.special >= 40) && (l.special <= 45))
|| (!part && (l.special == 46))
|| (part && (l.special == 47))
|| (!part && (l.special >= 60) && (l.special <= 68))
|| (part && (l.special == 69))
|| ((l.special >= 94) && (l.special <= 96))
|| (part && (l.special == 97))
|| (!part && (l.special == 99))
|| (part && (l.special == 104))
|| (part && (l.special >= 105) && (l.special <= 106))
|| (part && (l.special >= 168) && (l.special <= 169))
|| (!part && (l.special == 172))
|| (part && (l.special >= 192) && (l.special <= 199))
|| (!part && (l.special == 200))
|| (part && (l.special >= 201) && (l.special <= 202))
|| (!part && (l.special == 203))
|| (part && (l.special == 205))
|| (!part && (l.special >= 206) && (l.special <= 207))
|| (!part && (l.special == 228))
|| (!part && (l.special >= 230) && (l.special <= 231))
|| (!part && (l.special >= 238) && (l.special <= 242))
|| ((l.special >= 245) && (l.special <= 247))
|| (part && (l.special == 249))
|| (!part && (l.special >= 250) && (l.special <= 251))
|| (part && (l.special >= 251) && (l.special <= 255))
|| (!part && (l.special >= 256) && (l.special <= 261))
|| (part && (l.special >= 262) && (l.special <= 269))
|| (!part && (l.special == 275))
|| (part && (l.special == 276))
|| (!part && (l.special == 279))
|| (part && (l.special == 280)) )
{
let si = level.CreateSectorTagIterator(l.Args[0],l);
int idx;
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
}
}
ti = ThinkerIterator.Create("Actor");
Actor a;
while ( a = Actor(ti.Next()) )
{
if ( !a.special || !a.Args[0] ) continue;
if ( (part && (a.special >= 10) && (a.special <= 13))
|| (!part && (a.special >= 20) && (a.special <= 25))
|| (!part && (a.special == 28))
|| ((a.special >= 29) && (a.special <= 30))
|| (!part && (a.special >= 35) && (a.special <= 37))
|| (part && (a.special >= 40) && (a.special <= 45))
|| (!part && (a.special == 46))
|| (part && (a.special == 47))
|| (!part && (a.special >= 60) && (a.special <= 68))
|| (part && (a.special == 69))
|| ((a.special >= 94) && (a.special <= 96))
|| (part && (a.special == 97))
|| (!part && (a.special == 99))
|| (part && (a.special == 104))
|| (part && (a.special >= 105) && (a.special <= 106))
|| (part && (a.special >= 168) && (a.special <= 169))
|| (!part && (a.special == 172))
|| (part && (a.special >= 192) && (a.special <= 199))
|| (!part && (a.special == 200))
|| (part && (a.special >= 201) && (a.special <= 202))
|| (!part && (a.special == 203))
|| (part && (a.special == 205))
|| (!part && (a.special >= 206) && (a.special <= 207))
|| (!part && (a.special == 228))
|| (!part && (a.special >= 230) && (a.special <= 231))
|| (!part && (a.special >= 238) && (a.special <= 242))
|| ((a.special >= 245) && (a.special <= 247))
|| (part && (a.special == 249))
|| (!part && (a.special >= 250) && (a.special <= 251))
|| (part && (a.special >= 251) && (a.special <= 255))
|| (!part && (a.special >= 256) && (a.special <= 261))
|| (part && (a.special >= 262) && (a.special <= 269))
|| (!part && (a.special == 275))
|| (part && (a.special == 276))
|| (!part && (a.special == 279))
|| (part && (a.special == 280)) )
{
let si = level.CreateSectorTagIterator(a.Args[0]);
int idx;
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
}
}
return false;
}
// the stupidest thing ever, it's called BlockingLine but it's not always blocking us
static play bool BlockingLineIsBlocking( Actor a, int blockflags = Line.ML_BLOCKEVERYTHING, Line testline = null )
{
Line l = testline?testline:a.BlockingLine;
// not blocked
if ( !l ) return false;
// one-sided always blocking
if ( !l.sidedef[1] ) return true;
// same for block everything lines
if ( l.flags&blockflags ) return true;
// lower and upper bounds hit?
double afloor = l.frontsector.floorplane.ZAtPoint(a.pos.xy),
bfloor = l.backsector.floorplane.ZAtPoint(a.pos.xy),
aceil = l.frontsector.ceilingplane.ZAtPoint(a.pos.xy),
bceil = l.backsector.ceilingplane.ZAtPoint(a.pos.xy);
if ( (min(a.pos.z+a.height,a.ceilingz) > min(aceil,bceil)) || (max(a.pos.z,a.floorz) < max(afloor,bfloor)) )
return true;
// solid 3d floor bounds hit?
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
{
F3DFloor ff = l.frontsector.Get3DFloor(i);
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID)) ) continue;
double floor = ff.top.ZAtPoint(a.pos.xy);
double ceil = ff.bottom.ZAtPoint(a.pos.xy);
if ( (a.pos.z+a.height > ceil) && (a.pos.z < floor) )
return true;
}
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
{
F3DFloor ff = l.backsector.Get3DFloor(i);
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID)) ) continue;
double floor = ff.top.ZAtPoint(a.pos.xy);
double ceil = ff.bottom.ZAtPoint(a.pos.xy);
if ( (a.pos.z+a.height > ceil) && (a.pos.z < floor) )
return true;
}
return false;
}
static play Vector3 UseLinePos( Line l )
{
Vector3 al, ah, bl, bh;
if ( !l.sidedef[1] )
{
// just the whole line
al = (l.v1.p,l.frontsector.floorplane.ZatPoint(l.v1.p));
ah = (l.v1.p,l.frontsector.ceilingplane.ZatPoint(l.v1.p));
bl = (l.v2.p,l.frontsector.floorplane.ZatPoint(l.v2.p));
bh = (l.v2.p,l.frontsector.ceilingplane.ZatPoint(l.v2.p));
return (al+ah+bl+bh)*.25;
}
SecPlane highestfloor, lowestfloor, lowestceiling, highestceiling;
if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p))
&& (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) )
{
highestfloor = l.frontsector.floorplane;
lowestfloor = l.backsector.floorplane;
}
else
{
highestfloor = l.backsector.floorplane;
lowestfloor = l.frontsector.floorplane;
}
if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p))
&& (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) )
{
lowestceiling = l.frontsector.ceilingplane;
highestceiling = l.backsector.ceilingplane;
}
else
{
lowestceiling = l.backsector.ceilingplane;
highestceiling = l.frontsector.ceilingplane;
}
// try to guess what the part that triggers this is
if ( l.Activation&SPAC_Cross )
{
// pick the "intersection"
al = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
return (al+ah+bl+bh)*.25;
}
// check if lower part available
al = (l.v1.p,lowestfloor.ZatPoint(l.v1.p));
ah = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
bl = (l.v2.p,lowestfloor.ZatPoint(l.v2.p));
bh = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) )
return (al+ah+bl+bh)*.25;
// check if upper part available
al = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
ah = (l.v1.p,highestceiling.ZatPoint(l.v1.p));
bl = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
bh = (l.v2.p,highestceiling.ZatPoint(l.v2.p));
if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) )
return (al+ah+bl+bh)*.25;
// check for 3d floors
bool floorfound = false;
Vector3 fal, fah, fbl, fbh;
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
{
let ff = l.backsector.Get3DFloor(i);
fal = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fah = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fbl = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
fbh = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
// skip if higher, we'll go with the lowest 3d floor (may not be right, but whatever)
if ( floorfound && (fah.z > ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue;
al = fal;
ah = fah;
bl = fbl;
bh = fbh;
floorfound = true;
}
if ( floorfound ) return (al+ah+bl+bh)*.25;
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
{
let ff = l.frontsector.Get3DFloor(i);
fal = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fah = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fbl = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
fbh = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
// skip if higher, we'll go with the lowest 3d floor (may not be right, but whatever)
if ( floorfound && (fah.z > ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue;
al = fal;
ah = fah;
bl = fbl;
bh = fbh;
floorfound = true;
}
if ( floorfound ) return (al+ah+bl+bh)*.25;
// check for midtex
if ( !l.sidedef[0].GetTexture(1).IsNull() )
{
double ofs = l.sidedef[0].GetTextureYOffset(1);
Vector2 siz = TexMan.GetScaledSize(l.sidedef[0].GetTexture(1));
Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[0].GetTexture(1));
ofs += tofs.y;
ofs *= l.sidedef[0].GetTextureYScale(1);
siz.y *= l.sidedef[0].GetTextureYScale(1);
if ( l.flags&Line.ML_DONTPEGBOTTOM )
{
al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs);
bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs);
ah = al+(0,0,siz.y);
bh = bl+(0,0,siz.y);
}
else
{
ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs);
bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs);
al = ah-(0,0,siz.y);
bl = bh-(0,0,siz.y);
}
return (al+ah+bl+bh)*.25;
}
if ( !l.sidedef[1].GetTexture(1).IsNull() )
{
double ofs = l.sidedef[1].GetTextureYOffset(1);
Vector2 siz = TexMan.GetScaledSize(l.sidedef[1].GetTexture(1));
Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[1].GetTexture(1));
ofs += tofs.y;
ofs *= l.sidedef[1].GetTextureYScale(1);
siz.y *= l.sidedef[1].GetTextureYScale(1);
if ( l.flags&Line.ML_DONTPEGBOTTOM )
{
al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs);
bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs);
ah = al+(0,0,siz.y);
bh = bl+(0,0,siz.y);
}
else
{
ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs);
bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs);
al = ah-(0,0,siz.y);
bl = bh-(0,0,siz.y);
}
return (al+ah+bl+bh)*.25;
}
// just use the intersection
al = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
return (al+ah+bl+bh)*.25;
}
static bool, TextureID DefaceTexture( TextureID checkme )
{
String tn = TexMan.GetName(checkme);
// special case: alt texture names in Doom 2 In Spain Only
if ( (tn ~== "MARBFAC2") || (tn ~== "SP_MAR01") )
return true, TexMan.CheckForTexture("defaced_MARBFAC2");
if ( (tn ~== "MARBFAC3") || (tn ~== "SP_MAR02"))
return true, TexMan.CheckForTexture("defaced_MARBFAC3");
if ( (tn ~== "MARBFAC4") || (tn ~== "SP_MAR03") )
return true, TexMan.CheckForTexture("defaced_MARBFAC4");
if ( (tn ~== "MARBFACE") || (tn ~== "SP_MAR04") )
return true, TexMan.CheckForTexture("defaced_MARBFACE");
if ( (tn ~== "ZZWOLF2") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF2");
if ( (tn ~== "ZZWOLF3") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF3");
if ( (tn ~== "ZZWOLF4") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF4");
if ( (tn ~== "ZZWOLF6") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF6");
if ( (tn ~== "ZZWOLF7") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF7");
if ( (tn ~== "ZZWOLF12") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF12");
if ( (tn ~== "ZZWOLF13") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF13");
return false, checkme;
}
// iterate through polyobjects and see if this line is part of one (returning which, if any)
static bool IsPolyLine( Line l, out swwm_PolyobjectHandle o )
{
let pi = swwm_PolyobjectIterator.Create();
swwm_PolyobjectHandle p;
while ( p = pi.Next() )
{
if ( p.Lines.Find(l) >= p.Lines.Size() ) continue;
o = p;
return true;
}
o = null;
return false;
}
// checks if the specified world coordinate is inside the polyobject
// this check is very naive but it should handle most "normal" shapes
// (yeah, sorry if you somehow want to play this mod with lilith.pk3)
static bool PointInPolyobj( Vector2 p, swwm_PolyobjectHandle o )
{
// first pass, find which vertex out of all lines is closest
Vertex v = o.StartLine.v1;
double dist = (v.p-p).length();
foreach ( l:o.Lines )
{
double dist2 = (l.v1.p-p).length();
if ( dist2 < dist )
{
v = l.v1;
dist = dist2;
}
dist2 = (l.v2.p-p).length();
if ( dist2 < dist )
{
v = l.v2;
dist = dist2;
}
}
// second pass, find which two lines share that vertex
// (in theory there should only be two)
Line a = null, b = null;
foreach ( l:o.Lines )
{
if ( (l.v1 == v) || (l.v2 == v) )
{
if ( !a ) a = l;
else if ( !b ) b = l;
else break;
}
}
// is the point behind both lines?
return (PointOnLineSide(p,a) && PointOnLineSide(p,b));
}
}

View file

@ -0,0 +1,398 @@
// math stuff + gutamatics caching
Struct SWWMProjectionData
{
swwm_GM_Matrix wtc;
int viewx, viewy, vieww, viewh;
}
extend Class SWWMUtility
{
// gutamatics caching
static clearscope void PrepareProjData( out SWWMProjectionData d, Vector3 viewpos, double angle, double pitch, double roll, double fov )
{
double aspect = Screen.GetAspectRatio();
// vertical fov
double fovratio = (aspect>=1.3)?1.333333:aspect;
double fovy = 2.*atan(tan(clamp(fov,5,170)/2.)/fovratio);
// world→clip matrix
swwm_GM_Matrix view = swwm_GM_Matrix.view(viewpos,angle,pitch,roll);
swwm_GM_Matrix perp = swwm_GM_Matrix.perspective(fovy,aspect,5,65535);
d.wtc = perp.multiplyMatrix(view);
// screen coord data
int sblocks = CVar.FindCVar('screenblocks').GetInt();
int viewx, viewy, vieww, viewh;
[viewx, viewy, vieww, viewh] = Screen.GetViewWindow();
int sh = Screen.GetHeight();
int h = sh;
if ( sblocks < 10 ) h = (sblocks*sh/10)&~7;
int bottom = sh-(h+viewy-((h-viewh)/2));
d.viewx = viewx;
d.viewy = sh-bottom-h;
d.vieww = vieww;
d.viewh = h;
}
static clearscope Vector3 ProjectPoint( SWWMProjectionData d, Vector3 worldpos )
{
return d.wtc.multiplyVector3(worldpos).asVector3();
}
static clearscope Vector2 NDCToViewport( SWWMProjectionData d, Vector3 ndc )
{
return (d.viewx,d.viewy)+(((ndc.x+1)*d.vieww)/2,((-ndc.y+1)*d.viewh)/2);
}
// checks if a point is inside the viewport
static clearscope bool TestScreenBounds( SWWMProjectionData d, Vector2 vpos )
{
return ((vpos.x == clamp(vpos.x,d.viewx,d.viewx+d.vieww))
&& (vpos.y == clamp(vpos.y,d.viewy,d.viewy+d.viewh)));
}
// less code duplication
static clearscope void AdjustClean_1( out double x, out double y )
{
x = (x-160)*CleanXFac_1+(Screen.GetWidth()*.5);
y = (y-100)*CleanYFac_1+(Screen.GetHeight()*.5);
}
static clearscope void AdjustClean_1x( out double x )
{
x = (x-160)*CleanXFac_1+(Screen.GetWidth()*.5);
}
static clearscope void AdjustClean_1y( out double y )
{
y = (y-100)*CleanYFac_1+(Screen.GetHeight()*.5);
}
// Vector/Axis utility functions
static clearscope Vector3 Vec3FromAngles( double angle, double pitch )
{
return (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
}
static clearscope Vector3 CircleOffset( Vector3 y, Vector3 z, double angle, double radius )
{
return y*cos(angle)*radius+z*sin(angle)*radius;
}
static clearscope Vector3 ConeSpread( Vector3 x, Vector3 y, Vector3 z, double angle, double spread )
{
return (x+y*cos(angle)*spread+z*sin(angle)*spread).unit();
}
static clearscope Vector3 RotateVector3( Vector3 v, double angle )
{
Vector2 v2d = Actor.RotateVector(v.xy,angle);
return (v2d.x,v2d.y,v.z);
}
static clearscope Vector3 AngleToVector3( double angle, double length = 1. )
{
Vector2 v2d = Actor.AngleToVector(angle,length);
return (v2d.x,v2d.y,0);
}
// new GetAxes
static clearscope Vector3, Vector3, Vector3 GetAxes( double angle, double pitch, double roll )
{
Quat r = Quat.FromAngles(angle,pitch,roll);
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
}
// included here until Gutamatics updates to use native quaternions
static clearscope double, double, double ToAngles( Quat q )
{
double angle = 0., pitch = 0., roll = 0.;
double stest = q.z*q.x-q.w*q.y;
double angY = 2.*(q.w*q.z+q.x*q.y);
double angX = 1.-2.*(q.y*q.y+q.z*q.z);
if ( stest < -.4999995 )
{
angle = atan2(angY,angX);
pitch = 90.;
roll = swwm_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w)));
}
else if ( stest > .4999995 )
{
angle = atan2(angY,angX);
pitch = -90.;
roll = swwm_GM_GlobalMaths.Normalize180(angle+(2.*atan2(q.x,q.w)));
}
else
{
angle = atan2(angY,angX);
pitch = -asin(2.*stest);
roll = atan2(2.*(q.w*q.x+q.y*q.z),1.-2.*(q.x*q.x+q.y*q.y));
}
return angle, pitch, roll;
}
// for aiming and shooting
static clearscope Vector3 GetPlayerViewDir( Actor player )
{
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
return r*(1,0,0);
}
static play Vector3 GetPlayerAimDir( Actor player )
{
FTranslatedLineTarget t;
double pitch = player.BulletSlope(t);
Quat r;
if ( !t.linetarget ) r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
else r = Quat.FromAngles(player.angle+player.viewangle,pitch,player.roll+player.viewroll);
return r*(1,0,0);
}
static clearscope Vector3, Vector3, Vector3 GetPlayerAxes( Actor player )
{
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
}
static play Vector3, Vector3, Vector3 GetPlayerAxesAutoAimed( Actor player )
{
FTranslatedLineTarget t;
double pitch = player.BulletSlope(t);
Quat r;
if ( !t.linetarget ) r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
else r = Quat.FromAngles(player.angle+player.viewangle,pitch,player.roll+player.viewroll);
return r*(1,0,0), r*(0,-1,0), r*(0,0,1);
}
static clearscope Vector3 GetPlayerEye( Actor player )
{
if ( !player.viewpos )
return player.Vec2OffsetZ(0,0,player.player.viewz);
if ( player.viewpos.flags&VPSF_ABSOLUTEPOS )
return player.viewpos.offset;
Vector3 origin = player.Vec2OffsetZ(0,0,player.player.viewz);
if ( player.viewpos.flags&VPSF_ABSOLUTEOFFSET )
return level.Vec3Offset(origin,player.viewpos.offset);
Quat r = Quat.FromAngles(player.angle,player.pitch,player.roll); // viewangles are not used
return level.Vec3Offset(origin,r*player.viewpos.offset);
}
static clearscope Vector3 GetFireOffset( Actor player, double x, double y, double z )
{
Vector3 origin = GetPlayerEye(player);
Quat r = Quat.FromAngles(player.angle+player.viewangle,player.pitch+player.viewpitch,player.roll+player.viewroll);
return level.Vec3Offset(origin,r*(x,-y,z));
}
// thanks zscript
static clearscope double fract( double a )
{
return a-floor(a);
}
static clearscope double lerp( double a, double b, double theta )
{
return a*(1.-theta)+b*theta;
}
static clearscope Vector3 LerpVector3( Vector3 a, Vector3 b, double theta )
{
return a*(1.-theta)+b*theta;
}
static clearscope Vector2 LerpVector2( Vector2 a, Vector2 b, double theta )
{
return a*(1.-theta)+b*theta;
}
static clearscope Color LerpColor( Color a, Color b, double theta )
{
Color c = Color(
int(a.a*(1.-theta)+b.a*theta),
int(a.r*(1.-theta)+b.r*theta),
int(a.g*(1.-theta)+b.g*theta),
int(a.b*(1.-theta)+b.b*theta)
);
return c;
}
// this can probably be simplified, but I'm lazy
static clearscope Vector3 HSVtoRGB( Vector3 hsv )
{
Vector3 p;
p.x = abs(fract(hsv.x+1.)*6.-3.);
p.y = abs(fract(hsv.x+(2./3.))*6.-3.);
p.z = abs(fract(hsv.x+(1./3.))*6.-3.);
Vector3 p2;
p2.x = (1.-hsv.y)+clamp(p.x-1.,0.,1.)*hsv.y;
p2.y = (1.-hsv.y)+clamp(p.y-1.,0.,1.)*hsv.y;
p2.z = (1.-hsv.y)+clamp(p.z-1.,0.,1.)*hsv.y;
return p2*hsv.z;
}
// "fast" exponentiation with integer exponents using loops
static clearscope double IntPowF( double base, int exp )
{
if ( exp < 0 ) return 1./IntPowF(base,-exp);
if ( exp == 0 ) return 1.;
double rslt = base;
for ( int i=0; i<exp; i++ ) rslt *= base;
return rslt;
}
static clearscope int IntPow( int base, int exp )
{
if ( exp < 0 ) return int(1./IntPow(base,-exp));
if ( exp == 0 ) return 1;
int rslt = base;
for ( int i=0; i<exp; i++ ) rslt *= base;
return rslt;
}
// pitch from one actor's eyes to another based on a specific fraction of its height
// meant for player view, Actor.PitchTo is recommended for more "general" situations
static double PitchTo( Actor a, Actor b, double hfact = 1. )
{
if ( !a || !b ) return 0;
Vector3 thispos = a.player?a.Vec2OffsetZ(0,0,a.player.viewz):a.Vec3Offset(0,0,a.GetCameraHeight()-a.floorclip);
Vector3 otherpos = b.Vec3Offset(0,0,b.height*hfact);
Vector3 diff = level.Vec3Diff(thispos,otherpos);
return -atan2(diff.z,diff.xy.length());
}
// extruded box intersection check, useful when checking things that might be hit along a path
static clearscope bool ExtrudeIntersect( Actor a, Actor b, Vector3 range, int steps, int pad = 0 )
{
if ( steps <= 0 ) return BoxIntersect(a,b,pad:pad);
double step = 1./steps;
for ( double i=step; i<=1.; i+=step )
{
if ( BoxIntersect(a,b,range*i,pad) )
return true;
}
return false;
}
// box intersection check, for collision detection
static clearscope bool BoxIntersect( Actor a, Actor b, Vector3 ofs = (0,0,0), int pad = 0 )
{
Vector3 diff = level.Vec3Diff(level.Vec3Offset(a.pos,ofs),b.pos);
if ( (abs(diff.x) > (a.radius+b.radius+pad)) || (abs(diff.y) > (a.radius+b.radius+pad)) ) return false;
if ( (diff.z > a.height+pad) || (diff.z < -(b.height+pad)) ) return false;
return true;
}
// sphere intersection check, useful for proximity detection
static clearscope bool SphereIntersect( Actor a, Vector3 p, double radius )
{
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
Vector3 amin = ap+(-a.radius,-a.radius,0),
amax = ap+(a.radius,a.radius,a.height);
double distsq = 0.;
if ( p.x < amin.x ) distsq += (amin.x-p.x)*(amin.x-p.x);
if ( p.x > amax.x ) distsq += (p.x-amax.x)*(p.x-amax.x);
if ( p.y < amin.y ) distsq += (amin.y-p.y)*(amin.y-p.y);
if ( p.y > amax.y ) distsq += (p.y-amax.y)*(p.y-amax.y);
if ( p.z < amin.z ) distsq += (amin.z-p.z)*(amin.z-p.z);
if ( p.z > amax.z ) distsq += (p.z-amax.z)*(p.z-amax.z);
return (distsq <= (radius*radius));
}
// hitscan exit point calculation given actor, entry point and direction
static clearscope Vector3 TraceExit( Actor a, Vector3 p, Vector3 d )
{
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
Vector3 amin = ap+(-a.radius,-a.radius,0),
amax = ap+(a.radius,a.radius,a.height);
Vector3 tmax, div = (1./d.x,1./d.y,1./d.z);
if ( div.x < 0 ) tmax.x = (amin.x-p.x)*div.x;
else tmax.x = (amax.x-p.x)*div.x;
if ( div.y < 0 ) tmax.y = (amin.y-p.y)*div.y;
else tmax.y = (amax.y-p.y)*div.y;
if ( div.z < 0 ) tmax.z = (amin.z-p.z)*div.z;
else tmax.z = (amax.z-p.z)*div.z;
return level.Vec3Offset(p,min(min(tmax.x,tmax.y),tmax.z)*d);
}
// Liang-Barsky line clipping
static clearscope bool, Vector2, Vector2 LiangBarsky( Vector2 minclip, Vector2 maxclip, Vector2 v0, Vector2 v1 )
{
double t0 = 0., t1 = 1.;
double xdelta = v1.x-v0.x;
double ydelta = v1.y-v0.y;
double p, q, r;
for ( int i=0;i<4; i++ )
{
switch ( i )
{
case 0:
p = -xdelta;
q = -(minclip.x-v0.x);
break;
case 1:
p = xdelta;
q = (maxclip.x-v0.x);
break;
case 2:
p = -ydelta;
q = -(minclip.y-v0.y);
break;
case 3:
p = ydelta;
q = (maxclip.y-v0.y);
break;
}
if ( (p == 0.) && (q<0.) ) return false, (0,0), (0,0);
if ( p < 0 )
{
r = q/p;
if ( r > t1 ) return false, (0,0), (0,0);
else if ( r > t0 ) t0 = r;
}
else if ( p > 0 )
{
r = q/p;
if ( r < t0 ) return false, (0,0), (0,0);
else if ( r < t1 ) t1 = r;
}
}
Vector2 ov0 = v0+(xdelta,ydelta)*t0;
Vector2 ov1 = v0+(xdelta,ydelta)*t1;
return true, ov0, ov1;
}
static play bool InPlayerFOV( PlayerInfo p, Actor a, double maxdist = 0. )
{
double vfov = p.fov*.5;
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
let mo = p.camera;
if ( !mo ) return false;
Vector3 pp;
if ( !a.CheckSight(mo,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false;
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
else pp = mo.Vec3Offset(0,0,mo.GetCameraHeight());
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
if ( (abs(sc.x) > hfov) || (abs(sc.y) > vfov) ) return false;
if ( (maxdist > 0.) && (sc.z > maxdist) ) return false;
return true;
}
// ui-friendly version without CheckSight call
static clearscope bool InPlayerFOVSimple( PlayerInfo p, Actor a, double maxdist = 0. )
{
double vfov = p.fov*.5;
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
let mo = p.camera;
if ( !mo ) return false;
Vector3 pp;
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
else pp = mo.Vec3Offset(0,0,mo.GetCameraHeight());
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
if ( (abs(sc.x) > hfov) || (abs(sc.y) > vfov) ) return false;
if ( (maxdist > 0.) && (sc.z > maxdist) ) return false;
return true;
}
// calculate angle, pitch and yscale of YBeam based on direction vector and length
static clearscope double, double, double CalcYBeam( Vector3 dir, double dist )
{
if ( level.pixelstretch == 1. )
{
// ez modo
double angle = atan2(dir.y,dir.x);
double pitch = asin(-dir.z)+90;
return angle, pitch, dist;
}
dir.z *= level.pixelstretch;
double len = dir.length();
dir /= len;
double angle = atan2(dir.y,dir.x);
double pitch = asin(-dir.z)+90;
double yscale = dist*len;
return angle, pitch, yscale;
}
}

View file

@ -0,0 +1,135 @@
// string-related functions
extend Class SWWMUtility
{
// bit ugly, but it works
static clearscope void ThousandsStr( out String s, int col = Font.CR_UNDEFINED, String colstr = "" )
{
if ( (col < Font.CR_UNDEFINED) || (col >= Font.NUM_TEXT_COLORS) )
ThrowAbortException("col parameter out of range, use colstr for non-standard font colors.");
String nstr = s;
s.Truncate(0);
int len = nstr.CodePointCount();
int t = len;
if ( nstr.Left(1) == "-" ) t++;
for ( int i=0, pos=0; i<len; i++ )
{
int ch;
[ch, pos] = nstr.GetNextCodePoint(pos);
s.AppendCharacter(ch);
t = (t-1)%3;
if ( (pos < len) && !t )
{
if ( colstr != "" ) s.AppendFormat("\c[%s],\c-",colstr);
else if ( col != Font.CR_UNDEFINED ) s.AppendFormat("\c%c,\c-",0x61+col);
else s.AppendCharacter(0x2C);
}
}
}
static clearscope String ThousandsNum( int n, int col = Font.CR_UNDEFINED, String colstr = "", int digits = 0 )
{
String nstr;
if ( digits > 0 ) nstr = String.Format("%0*d",digits,n);
else nstr = String.Format("%d",n);
ThousandsStr(nstr,col,colstr);
return nstr;
}
static clearscope void StripColor( out String str )
{
int len = str.CodePointCount();
for ( int i=0, pos=0; i<len; i++ )
{
int remlen = 0;
int cplen = 0;
int ch, nxt;
[ch, nxt] = str.GetNextCodePoint(pos);
if ( ch != 0x1C )
{
pos = nxt;
continue;
}
remlen++;
cplen++;
[ch, nxt] = str.GetNextCodePoint(pos+remlen);
if ( ch == 0x5B )
{
int ch2;
do
{
[ch2, nxt] = str.GetNextCodePoint(pos+remlen);
remlen += nxt-(pos+remlen);
cplen++;
}
while ( ch2 != 0x5D );
}
remlen++;
str.Remove(pos,remlen);
len -= cplen;
i--;
}
}
static clearscope String SuperscriptNum( int val )
{
// unicode is fun
static const int digs[] = {0x2070,0x00B9,0x00B2,0x00B3,0x2074,0x2075,0x2076,0x2077,0x2078,0x2079};
String str = "";
int digits = int(Log10(val));
for ( int i=digits; i>=0; i-- )
{
int d = int(val/IntPow(10,i))%10;
str.AppendCharacter(digs[d]);
}
return str;
}
static clearscope void ObscureText( out String str, int seed, bool alnum = false )
{
int len = str.CodePointCount();
String newstr = "";
for ( int i=0, pos=0; i<len; i++ )
{
seed = ((seed<<1)*35447+(seed/87));
int ch;
[ch, pos] = str.GetNextCodePoint(pos);
if ( (ch == 0x20) || (ch == 0x09) || (ch == 0x0A) )
newstr.AppendCharacter(ch);
else if ( alnum )
{
int sd = abs(seed%36);
if ( sd >= 10 ) sd += 7;
newstr.AppendCharacter(sd+48);
}
else newstr.AppendCharacter((abs(seed)%95)+32);
}
str = newstr;
}
static clearscope void BeautifyClassName( out String str )
{
String workstr = str;
str.Truncate(0);
workstr.Replace("_"," ");
int len = workstr.CodePointCount();
for ( int i=0, pos=0; i<len; i++ )
{
int cp1;
[cp1, pos] = workstr.GetNextCodePoint(pos);
str.AppendCharacter(cp1);
if ( i < len-1 )
{
int cp2 = workstr.GetNextCodePoint(pos);
// this looks awkward, but I have to also account for non-letter characters
// uppercase after lowercase
if ( (String.CharUpper(cp1) != cp1) && (String.CharLower(cp2) != cp2) )
str.AppendCharacter(0x20);
// uppercase after non-letter
else if ( (String.CharUpper(cp1) == cp1) && (String.CharLower(cp1) == cp1) && (String.CharLower(cp2) != cp2) )
str.AppendCharacter(0x20);
// non-letter after lowercase
else if ( (String.CharUpper(cp1) != cp1) && (String.CharLower(cp2) == cp2) && (String.CharUpper(cp2) == cp2) )
str.AppendCharacter(0x20);
}
}
}
}