From 44f39d1024c265569828aee49ff57fe869865c9f Mon Sep 17 00:00:00 2001 From: Marisa the Magician Date: Thu, 15 Dec 2022 17:12:53 +0100 Subject: [PATCH] Split player and utility code. --- language.version | 4 +- zscript.txt | 26 +- zscript/handler/swwm_handler_iwantdie.zsc | 2 +- zscript/items/swwm_powerups_vip.zsc | 2 +- zscript/player/swwm_player.zsc | 763 +++ zscript/player/swwm_player_anim.zsc | 954 ++++ zscript/player/swwm_player_cheats.zsc | 359 ++ zscript/player/swwm_player_extra.zsc | 82 + zscript/player/swwm_player_face.zsc | 161 + zscript/{ => player}/swwm_player_fx.zsc | 0 zscript/player/swwm_player_inventory.zsc | 211 + zscript/{ => player}/swwm_player_items.zsc | 0 zscript/player/swwm_player_move.zsc | 640 +++ zscript/player/swwm_player_think.zsc | 332 ++ zscript/player/swwm_player_tick.zsc | 843 ++++ zscript/swwm_player.zsc | 4100 ----------------- zscript/swwm_utility.zsc | 2511 ---------- zscript/utility/swwm_utility.zsc | 124 + zscript/utility/swwm_utility_achievements.zsc | 86 + zscript/utility/swwm_utility_blast.zsc | 252 + zscript/utility/swwm_utility_compat.zsc | 153 + zscript/utility/swwm_utility_info.zsc | 440 ++ zscript/utility/swwm_utility_item.zsc | 327 ++ zscript/utility/swwm_utility_map.zsc | 622 +++ zscript/utility/swwm_utility_math.zsc | 398 ++ zscript/utility/swwm_utility_string.zsc | 135 + 26 files changed, 6908 insertions(+), 6619 deletions(-) create mode 100644 zscript/player/swwm_player.zsc create mode 100644 zscript/player/swwm_player_anim.zsc create mode 100644 zscript/player/swwm_player_cheats.zsc create mode 100644 zscript/player/swwm_player_extra.zsc create mode 100644 zscript/player/swwm_player_face.zsc rename zscript/{ => player}/swwm_player_fx.zsc (100%) create mode 100644 zscript/player/swwm_player_inventory.zsc rename zscript/{ => player}/swwm_player_items.zsc (100%) create mode 100644 zscript/player/swwm_player_move.zsc create mode 100644 zscript/player/swwm_player_think.zsc create mode 100644 zscript/player/swwm_player_tick.zsc delete mode 100644 zscript/swwm_player.zsc delete mode 100644 zscript/swwm_utility.zsc create mode 100644 zscript/utility/swwm_utility.zsc create mode 100644 zscript/utility/swwm_utility_achievements.zsc create mode 100644 zscript/utility/swwm_utility_blast.zsc create mode 100644 zscript/utility/swwm_utility_compat.zsc create mode 100644 zscript/utility/swwm_utility_info.zsc create mode 100644 zscript/utility/swwm_utility_item.zsc create mode 100644 zscript/utility/swwm_utility_map.zsc create mode 100644 zscript/utility/swwm_utility_math.zsc create mode 100644 zscript/utility/swwm_utility_string.zsc diff --git a/language.version b/language.version index b46af7f34..142cfb769 100644 --- a/language.version +++ b/language.version @@ -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-"; diff --git a/zscript.txt b/zscript.txt index 28c76e466..8d70ac544 100644 --- a/zscript.txt +++ b/zscript.txt @@ -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" diff --git a/zscript/handler/swwm_handler_iwantdie.zsc b/zscript/handler/swwm_handler_iwantdie.zsc index e65df8416..236c94fda 100644 --- a/zscript/handler/swwm_handler_iwantdie.zsc +++ b/zscript/handler/swwm_handler_iwantdie.zsc @@ -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 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 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 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"); + } +} diff --git a/zscript/player/swwm_player_anim.zsc b/zscript/player/swwm_player_anim.zsc new file mode 100644 index 000000000..41f40a25f --- /dev/null +++ b/zscript/player/swwm_player_anim.zsc @@ -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; + } +} diff --git a/zscript/player/swwm_player_cheats.zsc b/zscript/player/swwm_player_cheats.zsc new file mode 100644 index 000000000..28bf208bb --- /dev/null +++ b/zscript/player/swwm_player_cheats.zsc @@ -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)(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)(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)(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)(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 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)(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)(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)(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 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 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 type = name; + if ( !type || type.IsAbstract() ) + { + if ( CheckLocalView() ) + Console.Printf("'%s' is not a valid inventory item",name); + return; + } + CheatTake_Item(type,amount); + } + } +} diff --git a/zscript/player/swwm_player_extra.zsc b/zscript/player/swwm_player_extra.zsc new file mode 100644 index 000000000..e5e80b8d7 --- /dev/null +++ b/zscript/player/swwm_player_extra.zsc @@ -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; + } +} diff --git a/zscript/player/swwm_player_face.zsc b/zscript/player/swwm_player_face.zsc new file mode 100644 index 000000000..3fb4c48fc --- /dev/null +++ b/zscript/player/swwm_player_face.zsc @@ -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; + } +} diff --git a/zscript/swwm_player_fx.zsc b/zscript/player/swwm_player_fx.zsc similarity index 100% rename from zscript/swwm_player_fx.zsc rename to zscript/player/swwm_player_fx.zsc diff --git a/zscript/player/swwm_player_inventory.zsc b/zscript/player/swwm_player_inventory.zsc new file mode 100644 index 000000000..40230bbd2 --- /dev/null +++ b/zscript/player/swwm_player_inventory.zsc @@ -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 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; + } +} diff --git a/zscript/swwm_player_items.zsc b/zscript/player/swwm_player_items.zsc similarity index 100% rename from zscript/swwm_player_items.zsc rename to zscript/player/swwm_player_items.zsc diff --git a/zscript/player/swwm_player_move.zsc b/zscript/player/swwm_player_move.zsc new file mode 100644 index 000000000..abc5af2d3 --- /dev/null +++ b/zscript/player/swwm_player_move.zsc @@ -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 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 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; + } +} diff --git a/zscript/player/swwm_player_think.zsc b/zscript/player/swwm_player_think.zsc new file mode 100644 index 000000000..9c8dbb642 --- /dev/null +++ b/zscript/player/swwm_player_think.zsc @@ -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 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 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 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 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; + } +} diff --git a/zscript/player/swwm_player_tick.zsc b/zscript/player/swwm_player_tick.zsc new file mode 100644 index 000000000..b7d949e20 --- /dev/null +++ b/zscript/player/swwm_player_tick.zsc @@ -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 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 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 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= -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 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 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; + } + } + } +} diff --git a/zscript/swwm_player.zsc b/zscript/swwm_player.zsc deleted file mode 100644 index 0bc584aeb..000000000 --- a/zscript/swwm_player.zsc +++ /dev/null @@ -1,4100 +0,0 @@ -// 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 - } - - 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; - } - // 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; - } - // the give cheat, oh boy - 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)(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)(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)(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)(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 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)(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)(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)(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 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; - } - 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 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 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; - } - } - 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 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 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 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 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 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 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; - } - } - 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 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 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 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 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 CheckPoison() - { - // HAHA no - player.poisoncount = 0; - } - 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 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= -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 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 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; - } - } - } - 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 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 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; - } - 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.)); - } - 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 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 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 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; - } - 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 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; - } - 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); - } - } - 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 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 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; - } - 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(); - } - } - 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; - } - 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); - } - 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; - } - 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; - } - // 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; - } - // 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; - } - 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"); - } - 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; - } -} - -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; - } -} diff --git a/zscript/swwm_utility.zsc b/zscript/swwm_utility.zsc deleted file mode 100644 index 4e383d3b8..000000000 --- a/zscript/swwm_utility.zsc +++ /dev/null @@ -1,2511 +0,0 @@ -// Misc. Utility 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 -}; - -enum EExitType -{ - ET_Normal, - ET_Secret, - ET_EndGame, - ET_NewMap, -}; - -Struct SWWMProjectionData -{ - swwm_GM_Matrix wtc; - int viewx, viewy, vieww, viewh; -} - -Class SWWMUtility -{ - // achievement helpers - 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)); - } - - // gets the names of all mod cvars - static clearscope void GetCVars( out Array cvarlist ) - { - cvarlist.Clear(); - let lmp = Wads.CheckNumForFullname("cvarinfo.base"); - if ( lmp == -1 ) ThrowAbortException("'cvarinfo.base' not found"); - String dat = Wads.ReadLump(lmp); - Array 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 which ) - { - if ( !swwm_weapontooltips ) return; - CVar v = CVar.FindCVar('swwm_tooltipshown'); - String tt = v.GetString(); - Array 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")); - } - - // 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; - } - - // 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 0 ) nstr = String.Format("%0*d",digits,n); - else nstr = String.Format("%d",n); - ThousandsStr(nstr,col,colstr); - return nstr; - } - - // 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; - } - - static clearscope void StripColor( out String str ) - { - int len = str.CodePointCount(); - for ( int i=0, pos=0; 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= 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 0 ) return -asin(diff.z/dist); - return 0; - } - - 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; - } - - // how the fuck is this not available to ZScript? - // copied from P_PointOnLineSidePrecise() - 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() - 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 - 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); - } - - // 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; - } - - // 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; - } - - // 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 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); - } - - // 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= 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; - } - - // try to be as thorough as possible in checking if DEHACKED has altered this actor - static clearscope bool CheckDehackery( Class 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; - } - - // 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))); - } - - // 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 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 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; - } - - 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; - } - - 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; - } - - // 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; - } - - // 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; - } - - 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; - } - - // 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 MergeMonster( SWWMHandler hnd, Class 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 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; - } - - // 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 ceil) && (a.pos.z < floor) ) - return true; - } - for ( int i=0; i 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 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 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; - } - - // 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 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; - } - - // 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 i ) - { - if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class)(i)).PickupTag.."S"); - if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class)(i)).PickupTag.."S"); - return GetDefaultByType(i).GetTag(); - } - - 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)); - } - - // 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= 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; - } - - 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 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; - } - - // 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 itm, bool multi = false ) - { - int np = 0; - for ( int i=0; i0); - } - - // 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 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 PickPair( Class a, Class 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 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 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 PickSWWMSlot3() - { - return PickPair('Spreadgun','PuntzerBeta'); - } - // super shotgun spawn - static Class PickSWWMSlot4() - { - return PickPair('Wallbuster','PuntzerGamma'); - } - // chaingun spawn - static Class PickSWWMSlot5() - { - return PickPair('Eviscerator','HeavyMahSheenGun'); - } - // rocket launcher spawn - static Class PickSWWMSlot6() - { - return PickPair('Hellblazer','Quadravol'); - } - // first plasma rifle spawn - static Class PickSWWMSlot7() - { - return PickPair('Sparkster','ModernSparkster'); - } - // second plasma rifle spawn - static Class PickSWWMSlot8() - { - return PickPair('SilverBullet','RayKhom'); - } - // first bfg spawn - static Class 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 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 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 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 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(); - } - // 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; - } -} - -Class RadiusDebugSphere : SWWMNonInteractiveActor -{ - Default - { - RenderStyle "AddStencil"; - StencilColor "White"; - } - States - { - Spawn: - XZW1 A 1 BRIGHT A_FadeOut(); - Wait; - } -} - -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; - } -} diff --git a/zscript/utility/swwm_utility.zsc b/zscript/utility/swwm_utility.zsc new file mode 100644 index 000000000..aaac96cb3 --- /dev/null +++ b/zscript/utility/swwm_utility.zsc @@ -0,0 +1,124 @@ +// Misc. Utility code + +Class SWWMUtility +{ + // gets the names of all mod cvars + static clearscope void GetCVars( out Array cvarlist ) + { + cvarlist.Clear(); + let lmp = Wads.CheckNumForFullname("cvarinfo.base"); + if ( lmp == -1 ) ThrowAbortException("'cvarinfo.base' not found"); + String dat = Wads.ReadLump(lmp); + Array 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 which ) + { + if ( !swwm_weapontooltips ) return; + CVar v = CVar.FindCVar('swwm_tooltipshown'); + String tt = v.GetString(); + Array 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; + } +} diff --git a/zscript/utility/swwm_utility_achievements.zsc b/zscript/utility/swwm_utility_achievements.zsc new file mode 100644 index 000000000..ef52f680b --- /dev/null +++ b/zscript/utility/swwm_utility_achievements.zsc @@ -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)); + } +} diff --git a/zscript/utility/swwm_utility_blast.zsc b/zscript/utility/swwm_utility_blast.zsc new file mode 100644 index 000000000..03b0881c1 --- /dev/null +++ b/zscript/utility/swwm_utility_blast.zsc @@ -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 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 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; + } +} diff --git a/zscript/utility/swwm_utility_compat.zsc b/zscript/utility/swwm_utility_compat.zsc new file mode 100644 index 000000000..33651c530 --- /dev/null +++ b/zscript/utility/swwm_utility_compat.zsc @@ -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 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 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; + } +} diff --git a/zscript/utility/swwm_utility_info.zsc b/zscript/utility/swwm_utility_info.zsc new file mode 100644 index 000000000..05ff6c937 --- /dev/null +++ b/zscript/utility/swwm_utility_info.zsc @@ -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 MergeMonster( SWWMHandler hnd, Class 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 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 i ) + { + if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class)(i)).PickupTag.."S"); + if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class)(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 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; + } +} diff --git a/zscript/utility/swwm_utility_item.zsc b/zscript/utility/swwm_utility_item.zsc new file mode 100644 index 000000000..76d1ba17e --- /dev/null +++ b/zscript/utility/swwm_utility_item.zsc @@ -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 itm, bool multi = false ) + { + int np = 0; + for ( int i=0; i0); + } + + // 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 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 PickPair( Class a, Class 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 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 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 PickSWWMSlot3() + { + return PickPair('Spreadgun','PuntzerBeta'); + } + // super shotgun spawn + static Class PickSWWMSlot4() + { + return PickPair('Wallbuster','PuntzerGamma'); + } + // chaingun spawn + static Class PickSWWMSlot5() + { + return PickPair('Eviscerator','HeavyMahSheenGun'); + } + // rocket launcher spawn + static Class PickSWWMSlot6() + { + return PickPair('Hellblazer','Quadravol'); + } + // first plasma rifle spawn + static Class PickSWWMSlot7() + { + return PickPair('Sparkster','ModernSparkster'); + } + // second plasma rifle spawn + static Class PickSWWMSlot8() + { + return PickPair('SilverBullet','RayKhom'); + } + // first bfg spawn + static Class 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 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 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 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 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(); + } +} diff --git a/zscript/utility/swwm_utility_map.zsc b/zscript/utility/swwm_utility_map.zsc new file mode 100644 index 000000000..7047befc5 --- /dev/null +++ b/zscript/utility/swwm_utility_map.zsc @@ -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 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 ceil) && (a.pos.z < floor) ) + return true; + } + for ( int i=0; i 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 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 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)); + } +} diff --git a/zscript/utility/swwm_utility_math.zsc b/zscript/utility/swwm_utility_math.zsc new file mode 100644 index 000000000..d0df72bc8 --- /dev/null +++ b/zscript/utility/swwm_utility_math.zsc @@ -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 (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; + } +} diff --git a/zscript/utility/swwm_utility_string.zsc b/zscript/utility/swwm_utility_string.zsc new file mode 100644 index 000000000..45efed189 --- /dev/null +++ b/zscript/utility/swwm_utility_string.zsc @@ -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 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=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= 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