// 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 Array bumpvelz, bumppitch; 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; 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.25:.5; 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 for ( int i=0; i)(AllActorClasses[i]); 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 for ( int i=0; i)(AllActorClasses[i]); 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() { for ( int i=0; i)(AllActorClasses[i]); 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; for ( int i=0; i)(AllActorClasses[i]); 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 ) { for ( int i=0; i)(AllActorClasses[i]); 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 ) { for ( int i=0; i)(AllActorClasses[i]); if ( !type ) continue; let def = GetDefaultByType(type); if ( !def.Icon.isValid() ) continue; GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true); } } void CheatGive_Collectibles() { for ( int i=0; i)(AllActorClasses[i]); 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(AllActorClasses[i])); 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 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; for ( int i=0; i 0 ); for ( int i=0; i con; con.Clear(); con.Push(d.HitSector); int found; do { found = 0; for ( int i=0; i= 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 ); for ( int i=0; i con; con.Clear(); con.Push(d.HitSector); int found; do { found = 0; for ( int i=0; i= 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 ); for ( int i=0; i 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 Vector2 BobWeapon( double ticfrac ) { // non-mod weapons bob normally if ( !(player.ReadyWeapon is 'SWWMWeapon') ) return Super.BobWeapon(ticfrac); bool oldbob = !!(player.WeaponState&WF_WEAPONBOBBING); player.WeaponState |= WF_WEAPONBOBBING; // always bob Vector2 cur = Super.BobWeapon(ticfrac); if ( !oldbob ) player.WeaponState &= ~WF_WEAPONBOBBING; double fangle = 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; cur.y -= diffpitch; cur.x += diffx*4.; cur.y += diffy*4.; return cur*SWWMUtility.Lerp(oldlagready,lagready,ticfrac); } 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.Push(-lastvelz); double bpitch = 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); bpitch = min(bpitch+30,60); lastbump *= 1.3; 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++; } double newp = min(90,pitch+bpitch); bumppitch.Push(newp-pitch); A_SetPitch(newp,SPF_INTERPOLATE); 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 ( encroached && !encroached.bNOBLOOD && !encroached.bINVULNERABLE ) { encroached.TraceBleed(realdmg,self); encroached.SpawnBlood(pos,angle,realdmg); } } } for ( int i=0; i double.epsilon ) continue; bumpvelz.Delete(i--); } for ( int i=0; i double.epsilon ) continue; bumppitch.Delete(i--); } 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 ( 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; for ( int i=0; i= 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() { Super.CalcHeight(); // handle smooth step down (hacky but looks ok) player.viewz += ssup; ssup = max(0,(ssup*.7)-.25); } 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] = swwm_CoordUtil.GetAxes(pitch,angle,roll); Vector3 accel; if ( (player.cmd.upmove == -32768) || sendtoground ) { sendtoground = true; player.centering = true; accel = (0,0,-4096); } else accel = z*player.cmd.upmove*8.; accel *= fs/128.; vel = vel+accel/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.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] = swwm_CoordUtil.GetAxes(pitch,angle,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 { 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] = swwm_CoordUtil.GetAxes(pitch,angle,roll); int fm = player.cmd.forwardmove; int sm = player.cmd.sidemove; if ( !(fm|sm) ) fm = 1; if ( fm ) dodge += (fm>0)?X:-X; if ( sm ) dodge += (sm>0)?Y:-Y; if ( player.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++; double newp = min(90.,pitch+5.); bumppitch.Push(newp-pitch); A_SetPitch(newp,SPF_INTERPOLATE); } } 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; double newp = min(90.,pitch+3.); bumppitch.Push(newp-pitch); A_SetPitch(newp,SPF_INTERPOLATE); SetStateLabel("Boost"); } else { A_StartSound(walljump?"demolitionist/kick":"demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP); dashboost = 0.; double bpitch = clamp((vel.length()-10)/5.,0.,20.); double newp = min(90.,pitch+bpitch); bumppitch.Push(newp-pitch); A_SetPitch(newp,SPF_INTERPOLATE); // 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(); } } 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' ) Console.MidPrint(null,"swwmkeyget."..item.GetClassName()); } override bool UseInventory( Inventory item ) { let itemtype = item.GetClass(); if ( (player.cheats&CF_TOTALLYFROZEN) || isFrozen() ) return false; // do the key gesture if ( item is 'Key' ) { let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( hnd ) hnd.equinoxhack = true; let rep = (Class)(GetReplacement(item.GetClass())); if ( hnd ) hnd.equinoxhack = false; if ( rep ) { SWWMGesture.SetSpecialGesture(self,GetDefaultByType(rep).gesture); return false; } } if ( !Actor.UseInventory(item) ) { if ( player == players[consoleplayer] ) { if ( !(item is 'Weapon') && !(item is 'SWWMCollectible') ) 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; } }