// The Demolitionist Class Demolitionist : PlayerPawn { int last_jump_held, last_boost, last_kick; Vector3 dashdir; double dashfuel, dashboost; int dashcooldown, boostcooldown, fuelcooldown; bool dashsnd; bool sendtoground; bool key_reentrant; int lastdamage; transient int lastdamagetic; bool lastground; int lastgroundtic; double lastvelz, prevvelz; double ssup; transient CVar myvoice, mute, fly6dof, clrun; SWWMStats mystats; int cairtime; int lastmpain; double guideangle, guidepitch, guideroll; // for weapon bobbing stuff bool bumpdown; double bumpvelz; 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; Actor selflight; Actor oldencroached; Vector3 oldencroachedpos; Vector3 pretelepos; SWWMItemSense itemsense; int itemsense_cnt; int healcooldown, healtimer, oldhealth; bool scriptedinvul; Property DashFuel : dashfuel; Default { Speed 1; Radius 16; // should be 9 in theory, but it'd be too thin Height 56; Mass 500; PainChance 255; Player.DisplayName "Demolitionist"; Player.StartItem "ExplodiumGun"; Player.StartItem "DeepImpact"; Player.ViewHeight 52; Player.AirCapacity 0; Player.GruntSpeed 20; Player.SoundClass "demolitionist"; DamageFactor "Drowning", 0.; DamageFactor "Poison", 0.; DamageFactor "PoisonCloud", 0.; DamageFactor "Falling", 0.; Demolitionist.DashFuel 240.; +NOBLOOD; +DONTGIB; +NOICEDEATH; +DONTMORPH; +DONTDRAIN; } // oh yay, more cheat modification override void CheatGive( String name, int amount ) { if ( !player.mo || (player.health <= 0) ) return; int giveall = ALL_NO; if ( name ~== "all" ) giveall = ALL_YES; else if ( name ~== "everything" ) giveall = ALL_YESYES; if ( giveall || (name ~== "health") ) { if ( amount > 0 ) { health = min(health+amount,1000); player.health = health; } else player.health = health = 200; } if ( giveall || (name ~== "backpack") ) { // Select the correct type of backpack based on the game let type = (class)(gameinfo.backpacktype); let def = GetDefaultByType(type); if ( type ) GiveInventory(type,def.MaxAmount,true); if ( !giveall ) return; } if ( giveall || (name ~== "ammo") ) { // Find every unique type of ammo. Give it to the player if // he doesn't have it already, and set each to its maximum. for ( int i=0; i)(AllActorClasses[i]); if ( !type || (type.GetParentClass() != "Ammo") ) continue; // Only give if it's for a valid weapon, unless using "give everything" bool isvalid = false; for ( int j=0; j)(AllActorClasses[j]); if ( !type2 ) continue; let rep = GetReplacement(type2); if ( (rep != type2) && !(rep is "DehackedPickup") ) continue; readonly weap = GetDefaultByType(type2); if ( !player.weapons.LocateWeapon(type2) || (weap.bCheatNotWeapon && (giveall != ALL_YESYES)) ) continue; if ( (type2 is 'SWWMWeapon') && SWWMWeapon(weap).UsesAmmo(type) ) { isvalid = true; break; } if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) ) { isvalid = true; break; } } if ( !isvalid ) continue; let ammoitem = FindInventory(type); if ( !ammoitem ) { ammoitem = Inventory(Spawn(type)); ammoitem.AttachToOwner(self); ammoitem.Amount = ammoitem.MaxAmount; } else if ( ammoitem.Amount < ammoitem.MaxAmount ) ammoitem.Amount = ammoitem.MaxAmount; } if ( !giveall ) return; } if ( giveall || (name ~== "armor") ) { // Give only subclasses of SWWMArmor for ( int i=0; i)(AllActorClasses[i]); if ( !type || (type == 'SWWMArmor') ) continue; if ( GetReplacement(type) == type ) { let owned = FindInventory(type); if ( owned ) { owned.Amount = owned.MaxAmount; continue; } let item = Inventory(Spawn(type)); item.ClearCounters(); // don't increase item counts item.Amount = item.MaxAmount; if ( !item.CallTryPickup(self) ) item.Destroy(); } } // Also give spares for ( int i=0; i)(AllActorClasses[i]); if ( !type || (type == 'SWWMSpareArmor') ) continue; if ( GetReplacement(type) == type ) { let owned = FindInventory(type); if ( owned ) { owned.Amount = owned.MaxAmount; continue; } let item = Inventory(Spawn(type)); item.ClearCounters(); // don't increase item counts item.Amount = item.MaxAmount; if ( !item.CallTryPickup(self) ) item.Destroy(); } } if ( !giveall ) return; } if ( giveall || (name ~== "keys") ) { 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 ( (giveall == ALL_YESYES) || !def.bCheatNotWeapon ) GiveInventory(type,1,true); } } player.PendingWeapon = savedpending; if ( !giveall ) return; } if ( giveall || (name ~== "artifacts") ) { for ( int i=0; i)(AllActorClasses[i]); if ( !type ) continue; if ( !(gameinfo.gametype&(GAME_Hexen|GAME_Strife)) && (type is 'AmmoFabricator') ) continue; // no fabricators before hexen // Don't give maxed items let owned = FindInventory(type); if ( owned && (owned.Amount >= owned.MaxAmount) ) continue; let def = GetDefaultByType (type); if ( def.Icon.isValid() && !(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") && !(type is "Armor") && !(type is "Key") ) { // Do not give replaced items unless using "give everything" if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) ) GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true); } } if ( !giveall ) return; } if ( giveall || (name ~== "puzzlepieces") ) { for ( int i=0; i)(AllActorClasses[i]); if ( !type ) continue; let def = GetDefaultByType(type); if ( !def.Icon.isValid() ) continue; // Do not give replaced items unless using "give everything" if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) ) GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true); } if ( !giveall ) return; } if ( (giveall == ALL_YESYES) || (name ~== "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 ( !(gameinfo.gametype&def.avail) ) continue; let item = Inventory(Spawn(AllActorClasses[i])); SWWMCollectible(item).propagated = true; // no score or anims if ( !item.CallTryPickup(self) ) item.Destroy(); } } if ( giveall ) return; let type = name; if ( !type ) { if ( PlayerNumber() == consoleplayer ) A_Log(String.Format("Unknown item \"%s\"\n",name)); } else GiveInventory(type,amount,true); } override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { 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() { Super.GiveDefaultInventory(); // preloaded gun let eg = ExplodiumGun(FindInventory("ExplodiumGun")); if ( !eg ) return; eg.clipcount = eg.default.clipcount; eg.chambered = true; let eg2 = DualExplodiumGun(eg.SisterWeapon); if ( !eg2 ) return; eg2.clipcount = eg2.default.clipcount; eg2.chambered = true; } 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 is 'HammerspaceEmbiggener') ) { i.DepleteOrDestroy(); if ( !i.bDestroyed ) last = i; } else last = i; } } override void PostBeginPlay() { Super.PostBeginPlay(); mystats = SWWMStats.Find(player); lastground = true; } void A_Dash() { vel += dashdir*dashboost; player.vel = vel.xy; if ( dashboost < .2 ) dashboost = 0.; else { if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:800); dashboost *= (player.cmd.buttons&BT_USER2)?.9:.1; } mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost); if ( !swwm_superfuel ) dashfuel = max(0.,dashfuel-dashboost); dashcooldown = min(40,max(10,int(dashcooldown*1.4))); fuelcooldown = max(30,fuelcooldown); if ( (dashfuel <= 0.) || (dashboost <= 0.) ) SetStateLabel("DashEnd"); } void A_BoostUp( bool initial = false ) { vel += (0,0,1)*dashboost; player.vel = vel.xy; if ( dashboost < .2 ) dashboost = 0.; else { if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:800); dashboost *= (player.cmd.buttons&BT_JUMP)?.95:.4; last_boost = level.maptime+1; } mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost); 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() { let thesight = FindInventory("Omnisight"); let bt = BlockThingsIterator.Create(self,800); while ( bt.Next() ) { let i = Inventory(bt.Thing); if ( !i || i.bINVISIBLE || !i.bSPECIAL || i.Owner || !SWWMUtility.SphereIntersect(i,pos,800) ) continue; if ( !thesight && !CheckSight(i,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; SWWMItemSense.Spawn(self,i); } } 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_UNDERWATER) ) // 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 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 = oldangle*(1.-ticfrac)+angle*ticfrac; double fpitch = (oldpitch*(1.-ticfrac)+pitch*ticfrac); double flagangle = (oldlagangle*(1.-ticfrac)+lagangle*ticfrac); double flagpitch = (oldlagpitch*(1.-ticfrac)+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 = oldlagvel*(1.-ticfrac)+lagvel*ticfrac; double diffx = flagvel dot (cos(flagangle+90),sin(flagangle+90),0); 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; } // don't do inertial sway when in 6dof mode, causes issues if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',player); if ( !fly6dof.GetBool() || !((waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2)) ) { cur.x += diffang; cur.y -= diffpitch; cur.x += diffx*4.; cur.y += diffy*4.; } return cur*(oldlagready*(1.-ticfrac)+lagready*ticfrac); } override void PlayerThink() { oldangle = angle; oldpitch = pitch; Super.PlayerThink(); oldlagangle = lagangle; oldlagpitch = lagpitch; lagangle = lagangle*.8+angle*.2; lagpitch = lagpitch*.8+pitch*.2; if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) ) { // faster falloff player.jumptics -= 5; if ( player.onground && (player.jumptics < -18) ) player.jumptics = 0; } // quick grenade if ( (player.playerstate != PST_DEAD) && (player.cmd.buttons&BT_USER4) ) SWWMGesture.SetGesture(self,GS_Grenade); } override void Tick() { Vector3 oldpos = pos; Super.Tick(); // can't be poisoned PoisonDurationReceived = 0; PoisonPeriodReceived = 0; PoisonDamageReceived = 0; if ( !player ) return; if ( !selflight ) { selflight = Spawn("DemolitionistSelfLight",pos); selflight.target = self; selflight.Tick(); } // strife thing if ( mystats.oldlogtext == "" ) mystats.oldlogtext = player.logtext; else if ( player.logtext != mystats.oldlogtext ) { mystats.questbacklog.Push(mystats.oldlogtext); mystats.oldlogtext = player.logtext; } // overheal fading if ( (health <= 200) || (health > oldhealth) ) { healtimer = 0; healcooldown = 80; } else if ( health > 200 ) { if ( healcooldown > 0 ) healcooldown--; else { if ( health > 500 ) { if ( !FindInventory("GrilledCheeseSafeguard") && !(healtimer%5) ) A_SetHealth(health-1); } else if ( health > 200 ) { if ( !FindInventory("RefresherRegen") && !(healtimer%15) ) 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 ( waterlevel < 2 ) { if ( !player.onground || bNoGravity ) { cairtime++; if ( cairtime > mystats.airtime ) mystats.airtime = cairtime; mystats.airdist += traveldist; } else { cairtime = 0; mystats.grounddist += traveldist; } } else mystats.swimdist += traveldist; CheckUnderwaterAmb(); if ( player.cmd.buttons&BT_USER3 ) SenseItems(); if ( vel.length() > mystats.topspeed ) mystats.topspeed = vel.length(); if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player); if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player); if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) ) { // bump down weapon bumpdown = true; bumpvelz = -lastvelz; if ( lastvelz < -25 ) { let s = Spawn("DemolitionistShockwave",pos); s.target = self; s.special1 = int(-lastvelz); 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); } mystats.stompcount++; } if ( lastvelz < -10 ) A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP); if ( (player == players[consoleplayer]) && (lastvelz < -gruntspeed) && (mute.GetInt() < 4) && (health > 0) ) A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP); if ( lastvelz < -1 ) A_Footstep(0,true,clamp(-lastvelz*0.05,0.0,1.0)); } // crush anything we're standing on bool dummy; Actor encroached; [dummy, encroached] = TestMobjZ(); // add special check so corpses don't get stuck on top of monsters and players if ( encroached && encroached.bSOLID && (bSOLID || encroached.bACTLIKEBRIDGE) ) { // try to follow movement (this method is awkward but works with monsters) if ( encroached == oldencroached ) { Vector3 oldp = pos; Vector3 newp = level.Vec3Offset(pos,level.Vec3Diff(oldencroachedpos,encroached.pos)); if ( level.IsPointInLevel(newp) ) { SetOrigin(newp,true); if ( !TestMobjLocation() ) SetOrigin(oldp,true); } } oldencroached = encroached; oldencroachedpos = encroached.pos; } else { // launch in movement direction (useful for moving platforms) // make sure we're not getting launched because an enemy just died under our feet, because that can cause some issues if ( oldencroached && (dashboost <= 0.) && (lastvelz >= -25) && (!oldencroached.bISMONSTER || (oldencroached.Health > 0)) ) vel += oldencroached.vel+level.Vec3Diff(oldencroachedpos,oldencroached.pos); oldencroached = null; } 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.bNOBLOOD && !encroached.bINVULNERABLE ) { encroached.TraceBleed(realdmg,self); encroached.SpawnBlood(pos,angle,realdmg); } } } if ( bumpdown ) { lagvel.z += bumpvelz*.2; bumpvelz *= .8; if ( bumpvelz < double.epsilon ) bumpdown = false; } lastground = player.onground; lastvelz = prevvelz; prevvelz = vel.z; bool isdashing = InStateSequence(CurState,FindState("Dash")); bool isboosting = InStateSequence(CurState,FindState("Jump")); bNOFRICTION = (((waterlevel<2)&&(bFly&&!bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||isdashing); fuelcooldown = max(0,fuelcooldown-1); dashcooldown = max(0,dashcooldown-1); boostcooldown = max(0,boostcooldown-1); if ( fuelcooldown <= 0 ) dashfuel = min(default.dashfuel,dashfuel+clamp(dashfuel*.025,.1,3.)); if ( (dashboost > 0.) && (isdashing || (isboosting && player.cmd.buttons&BT_JUMP)) ) dashsnd = true; else { if ( dashsnd ) A_StartSound("demolitionist/jetstop",CHAN_JETPACK); dashsnd = false; } PainChance = isdashing?0:255; if ( isdashing || (vel.length() > 50) ) { 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.target = 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 = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(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 ) continue; if ( !SWWMUtility.ExtrudeIntersect(self,a,dir*spd,8,16) ) continue; if ( !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; Vector3 diff = level.Vec3Diff(pos,a.pos); Vector3 dirto = diff.unit(); if ( dir dot dirto < .1 ) continue; if ( a.bACTLIKEBRIDGE && (diff.z <= -a.Height) ) continue; // don't bump bridges if hit by above // 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); 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.); if ( (diff.z < a.height) && (lastvelz >= -25) && (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; A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN); 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*(15+(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(15+spd*2.5); 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,CHAN_OVERLAP,1.,.2); Spawn("SWWMItemFog",level.Vec3Offset(pos,diff/2)); A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:1.); mystats.buttslams++; } } 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 A_SprayDecal("ButtMark",172,(cos(angle+90)*2,sin(angle+90)*2,Height*.54),dir); A_SprayDecal("ButtMark",172,(cos(angle-90)*2,sin(angle-90)*2,Height*.54),dir); } if ( raging || swwm_omnibust ) { // see if we can bust it let tempme = new("LineTracer"); // gross hack to pass needed data int dmg = int(15+spd*2.5); 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/2) ) { // 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(15+spd*2.5); raging.DoHitFX(); } A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP); if ( buttslam ) { A_StartSound("demolitionist/buttslam",CHAN_DAMAGE,CHANF_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,falloff:300,rollIntensity:1.); mystats.buttslams++; } if ( raging ) continue; // don't stop } } // wallbump bumped = true; A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP); bumptic = gametic+int(20+spd/4.); A_QuakeEx(8,8,8,16,0,128,"",QF_RELATIVE|QF_SCALEDOWN); 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(15+spd*2.5); raging.DoHitFX(); } // activate it int locknum = SWWMUtility.GetLineLock(BlockingLine); if ( !locknum || CheckKeys(locknum,false,true) ) BlockingLine.Activate(self,lside,SPAC_Use); BlockingLine.Activate(self,lside,SPAC_Impact); break; } } SetOrigin(oldpos,true); bTHRUACTORS = oldthru; } else if ( isboosting && (dashboost > 0.) ) { Actor a; for ( int i=-1; i<=1; i+=2 ) for ( int j=1; j<4; j++ ) { a = Spawn("DashTrail",Vec3Angle(10,angle+i*140,40)); a.target = self; a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j; a.vel -= vel*.5; } } } override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle ) { if ( (swwm_strictuntouchable >= 2) && (damage > 0) && player ) { let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( hnd ) hnd.tookdamage[PlayerNumber()] = true; } // no damage whatsoever if ( scriptedinvul ) return 0; if ( damage <= 0 ) { lastdamage = 0; return Super.DamageMobj(inflictor,source,damage,mod,flags,angle); } // lucky collar if ( (source == self) ) damage = max(1,damage/2); if ( (Health-damage < 25) ) { int splitdmg[2]; splitdmg[0] = max(0,Health-25); // non-reduced part (>=25% health) splitdmg[1] = max(1,(damage-splitdmg[0])/4); // reduced part (<25% health) damage = splitdmg[0]+splitdmg[1]; } if ( !inflictor && !source && (FloorSector.flags&Sector.SECF_ENDLEVEL) ) { // end level hax damage = max(50,health-100); flags |= DMG_FORCED|DMG_NO_ARMOR; mod = 'EndLevel'; } int oldpchance = PainChance; if ( damage < 5 ) PainChance = 0; lastdamage = Super.DamageMobj(inflictor,source,damage,mod,flags,angle); lastdamagetic = max(lastdamagetic,gametic+clamp(lastdamage/2,10,40)); if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) ) { lastmpain = level.maptime; A_DemoPain(); } PainChance = oldpchance; return lastdamage; } 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 ( (waterlevel < 2) && 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 ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) { double fs = TweakSpeeds(1.,0.); 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/TICRATE; 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(); } override void MovePlayer() { if ( !player || (player.mo != self) || (player.cheats&(CF_FROZEN|CF_TOTALLYFROZEN)) ) { dashboost = 0.; if ( dashsnd ) A_StartSound("demolitionist/jetstop",CHAN_JETPACK); dashsnd = false; Super.MovePlayer(); return; } bool isdashing = InStateSequence(CurState,FindState("Dash")); if ( isdashing ) player.cmd.forwardmove = player.cmd.sidemove = 0; if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) { player.onground = false; if ( player.turnticks ) { player.turnticks--; guideangle = (180./TURN180_TICKS); } else guideangle += .2*player.cmd.yaw*(360./65536.); guidepitch -= .2*player.cmd.pitch*(360./65536.); if ( !fly6dof ) fly6dof = CVar.GetCVar('swwm_fly6dof',player); if ( player.centering ) guidepitch = clamp(deltaangle(pitch,0),-3.,3.); if ( player.centering || !fly6dof.GetBool() ) guideroll = clamp(deltaangle(roll,0),-3.,3.); if ( fly6dof.GetBool() ) { swwm_Quat orient = swwm_Quat.create_euler(pitch,angle,roll); swwm_Quat angles = swwm_Quat.create_euler(guidepitch,guideangle,guideroll); orient = orient.qmul(angles); double npitch, nangle, nroll; [npitch, nangle, nroll] = orient.to_euler(); A_SetAngle(nangle,SPF_INTERPOLATE); A_SetPitch(npitch,SPF_INTERPOLATE); A_SetRoll(nroll,SPF_INTERPOLATE); } else { 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 = TweakSpeeds(1.,0.); 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); Vector3 accel = x*player.cmd.forwardmove+y*player.cmd.sidemove+z*jcmove; accel *= fs/320.; double spd = vel.length(); if ( spd > 40. ) vel = (vel+accel/TICRATE).unit()*spd; else vel = vel+accel/TICRATE; vel *= .97; 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 { Super.MovePlayer(); // override air control because we REALLY need the extra mobility if ( !player.onground && !bNOGRAVITY && (waterlevel < 2) ) { double fs = TweakSpeeds(1.,0.); if ( CanCrouch() && (player.crouchfactor != -1) ) fs *= player.crouchfactor; Vector3 accel = (RotateVector((player.cmd.forwardmove,-player.cmd.sidemove),angle),0); accel *= fs/512.; double spd = vel.length(); if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd; else vel = vel+accel/TICRATE; } else if ( player.onground && !bNOGRAVITY && (waterlevel < 2) ) { // quickly decelerate if we're not holding movement keys if ( !(player.cmd.forwardmove|player.cmd.sidemove) ) vel *= .97; } 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 && (waterlevel < 2) && (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; 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) ) dodge.xy = RotateVector((1,0),angle); if ( !level.IsJumpingAllowed() ) dodge.z = min(0,dodge.z); if ( player.onground ) dodge.z = max(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) ) { dashdir = dodge.unit(); dashcooldown = 10; dashboost = 20.; bOnMobj = false; if ( player.cheats & CF_REVERTPLEASE ) { player.cheats &= ~CF_REVERTPLEASE; player.camera = player.mo; } vel.z += clamp(-vel.z*.4,0.,30.); player.jumptics = -1; SetStateLabel("Dash"); A_StartSound("demolitionist/jet",CHAN_JETPACK,CHANF_LOOP); mystats.dashcount++; } } override void CheckJump() { if ( InStateSequence(CurState,FindState("Dash")) ) return; // do not Vector2 walldir = (cos(angle),sin(angle)); bool walljump = false, wallclimb = false; FLineTraceData d; Actor jumpactor = null; for ( int i=-4; i= 2 ) vel.z = 4*Speed; else if ( (waterlevel < 2) && bFly && !bFlyCheat && !(player.cheats&CF_NOCLIP2) ) return; else if ( bNoGravity ) vel.z = 3; else if ( level.IsJumpingAllowed() && ((player.onground && (player.jumptics == 0)) || (!player.onground && (level.maptime > last_jump_held) && (((dashfuel > 10.) && (boostcooldown <= 0)) || walljump || wallclimb))) ) { if ( !player.onground && (((walljump || wallclimb) && (level.maptime < last_kick+8)) || (!(walljump || wallclimb) && (level.maptime < last_boost+8))) ) return; double jumpvelz = JumpZ*35./TICRATE; 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; if ( FindInventory("RagekitPower") ) 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 += jumpvelz+clamp(-pvelz*.6,0.,30.); vel.xy += walldir*20*Speed; } else if ( wallclimb ) { if ( vel.z < 10. ) vel.z += jumpvelz+clamp(-pvelz*.8,0.,30.); vel.xy -= walldir*5*Speed; } if ( jumpactor && jumpactor.bSHOOTABLE ) { SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000); int dmg = jumpactor.DamageMobj(self,self,10,'Jump'); if ( FindInventory("RagekitPower") ) { let ps = Spawn("BigPunchSplash",pos); ps.damagetype = 'Jump'; ps.target = self; ps.special1 = dmg; } } if ( walljump || wallclimb ) { A_StartSound("demolitionist/kick",CHAN_FOOTSTEP,CHANF_OVERLAP); last_kick = level.maptime+1; } } bOnMobj = false; player.jumpTics = -1; if ( !(player.cheats&CF_PREDICTING) ) A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP); 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); mystats.boostcount++; last_boost = level.maptime+1; } else { dashboost = 0.; if ( vel.z < 10. ) vel.z += jumpvelz*1.25; } SetStateLabel("Jump"); } last_jump_held = level.maptime+1; } } 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--; 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) && !player.Bot) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn && (Level.maptime >= player.respawn_time))) && !sv_norespawn ) { 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) && swwm_revive ) { // reboot (if possible) if ( !FindInventory("ReviveCooldown") ) { player.Resurrect(); 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); } SWWMHandler.DoFlash(self,Color(255,255,255,255),10); SWWMHandler.DoFlash(self,Color(255,128,192,255),30); SWWMScoreObj.Spawn(default.health,Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Height/2),Font.CR_BLUE); if ( special1 > 2 ) special1 = 0; if ( swwm_revivecooldown > 0 ) 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 && (waterlevel < 3) ) { // Ground if ( player.crouchdir == -1 ) { // Crouching if ( InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("Crouch"); else if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See")) || InStateSequence(CurState,FindState("SeeFast")) || InStateSequence(CurState,FindState("SeeFastLoop")) || InStateSequence(CurState,FindState("SeeFastEnd")) || InStateSequence(CurState,FindState("Float")) || InStateSequence(CurState,FindState("Swim")) || InStateSequence(CurState,FindState("SwimLoop")) ) SetStateLabel("StartCrouch"); } else { if ( InStateSequence(CurState,FindState("Crouch")) || InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("EndCrouch"); else if ( InStateSequence(CurState,FindState("See")) || 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")) ) SetStateLabel("SwimEnd"); } } else if ( !bNoGravity && (waterlevel < 1) ) { // Falling if ( player.crouchdir == -1 ) { if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See")) || 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")) ) SetStateLabel("StartCrouch"); } else { if ( (InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See")) || 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")) ) SetStateLabel("EndCrouch"); else if ( InStateSequence(CurState,FindState("Swim")) || InStateSequence(CurState,FindState("SwimLoop")) ) 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("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")) ) SetStateLabel("StartCrouch"); } else { if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See")) || 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")) ) SetStateLabel("SwimEnd"); else if ( InStateSequence(CurState,FindState("Crouch")) || InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("EndCrouch"); } } } override void PlayRunning() { if ( !player ) { if ( !InStateSequence(CurState,FindState("See")) ) SetStateLabel("See"); return; } if ( player.health <= 0 ) return; if ( !bNoGravity && player.onground && (waterlevel < 3) ) { // Ground if ( player.crouchdir == -1 ) { // Crouching if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See")) || 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")) ) SetStateLabel("StartCrouch"); else if ( InStateSequence(CurState,FindState("Crouch")) ) SetStateLabel("CrouchMove"); } else { if ( InStateSequence(CurState,FindState("Crouch")) || InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("EndCrouch"); else if ( InStateSequence(CurState,FindState("Swim")) || InStateSequence(CurState,FindState("SwimLoop")) ) SetStateLabel("SwimEnd"); else if ( FastCheck() && (InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See"))) ) SetStateLabel("SeeFast"); else if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Float")) || InStateSequence(CurState,FindState("Turn")) ) { SetStateLabel("See"); A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2); } } } else if ( !bNoGravity && (waterlevel < 1) ) { // 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("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")) ) SetStateLabel("StartCrouch"); else if ( InStateSequence(CurState,FindState("Crouch")) ) SetStateLabel("CrouchMove"); } 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("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")) ) SetStateLabel("Float"); else if ( InStateSequence(CurState,FindState("Crouch")) || InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("EndCrouch"); } else { if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See")) || 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("CrouchMove")) ) 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("Jump")) ) return; // don't cancel dash/jump if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile"); else SetStateLabel("Missile"); } override void PlayAttacking2() { PlayAttacking(); } void PlayFire() { if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Jump")) ) return; // don't cancel dash/jump if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMissile"); else SetStateLabel("Missile"); } void PlayMelee() { if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Jump")) ) return; // don't cancel dash/jump if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchMelee"); else SetStateLabel("Melee"); } void PlayFastMelee() { if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Jump")) ) return; // don't cancel dash/jump if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchFastMelee"); else SetStateLabel("FastMelee"); } void PlayReload() { if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Jump")) ) return; // don't cancel dash/jump if ( player && (player.crouchdir == -1) ) SetStateLabel("CrouchReload"); else SetStateLabel("Reload"); } void PlayCheckGun() { if ( InStateSequence(CurState,FindState("Dash")) || InStateSequence(CurState,FindState("Jump")) ) return; // don't cancel dash/jump 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 ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player); if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player); if ( lastdamage > 90 ) { A_StartSound("demolitionist/hipain",CHAN_VOICE); if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) ) A_StartSound(String.Format("voice/%s/hipain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP); } else if ( lastdamage > 30 ) { A_StartSound("demolitionist/pain",CHAN_VOICE); if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) ) A_StartSound(String.Format("voice/%s/pain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP); } else if ( lastdamage > 0 ) { A_StartSound("demolitionist/lopain",CHAN_VOICE); if ( !player || (player.mo != self) || (lastdamage < 5) ) return; // voodoo dolls have no voice if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) ) A_StartSound(String.Format("voice/%s/lopain",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP); } } void A_DemoScream() { A_StopSound(CHAN_DEMOVOICE); if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player); if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player); Sound snd = "demolitionist/death"; if ( special1 < 10 ) snd = "demolitionist/wdeath"; if ( health < 50 ) snd = "demolitionist/xdeath"; A_StartSound(snd,CHAN_VOICE); if ( !player || (player.mo != self) ) return; // voodoo dolls have no voice if ( (player == players[consoleplayer]) && mute && myvoice && (mute.GetInt() < 4) ) A_StartSound(String.Format("voice/%s/death",myvoice.GetString()),CHAN_DEMOVOICE,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 ( (level.found_secrets == level.total_secrets-1) && (!hnd || !hnd.allsecrets) ) { if ( hnd ) hnd.allsecrets = true; score = 1000; Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),player.GetUserName(),score); } else Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score); if ( CheckLocalView() ) SWWMHandler.AddOneliner("findsecret",2,40); SWWMCredits.Give(player,score); SWWMScoreObj.Spawn(score,Vec3Offset(0,0,Height/2)); mystats.secrets++; return true; } override void AddInventory( Inventory item ) { Super.AddInventory(item); if ( !player ) return; // add lore if any SWWMLoreLibrary.Add(player,item.GetClassName()); if ( (item is 'Weapon') && !(item is 'SWWMGesture') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() ) SWWMHandler.AddOneliner("getweapon",2); if ( (item is 'Key') && !key_reentrant && !deathmatch ) { // score int score = 100; Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score); SWWMCredits.Give(player,score); SWWMScoreObj.Spawn(100,Vec3Offset(0,0,Height/2)); if ( !swwm_sharekeys ) return; // share all keys in mp for ( int i=0; i 0) ) { SWWMHandler.AddOneliner("greet",2); return true; } return false; } bool FastCheck( bool notfast = false ) { if ( !player ) return false; bool rslt = player.cmd.buttons&BT_RUN; if ( notfast ) rslt = !rslt; return rslt; } 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) ) { if ( waterlevel >= 2 ) vel.z = -4*Speed; else if ( 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 Travelled() { // reinitialize dashfuel = default.dashfuel; last_boost = 0; last_kick = 0; // cancel dash/boost A_StopSound(CHAN_JETPACK); fuelcooldown = 0.; dashcooldown = 0.; dashboost = 0.; dashsnd = false; // prevent sudden stomping if we were previously falling lastvelz = vel.z; // early cancel gestures if ( player && (player.ReadyWeapon is 'SWWMGesture') ) { player.PendingWeapon = SWWMGesture(player.ReadyWeapon).formerweapon; player.SetPSprite(PSP_WEAPON,player.ReadyWeapon.ResolveState("Deselect")); } // re-attach shadow if ( swwm_shadows <= 0 ) return; let ti = ThinkerIterator.Create("SWWMShadow"); Actor a; while ( a = Actor(ti.Next()) ) { if ( a.target == self ) return; // shadow already attached } SWWMShadow.Track(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 ) { // subtract travel distance from the stats, and add it to a separate stat double traveldist = level.Vec3Diff(pretelepos,pos).length(); if ( waterlevel < 2 ) { if ( !player.onground || bNoGravity ) mystats.airdist -= traveldist; else mystats.grounddist -= traveldist; } else mystats.swimdist -= traveldist; mystats.teledist += traveldist; // 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; // notify carried lamp that we just moved let l = SWWMLamp(FindInventory("SWWMLamp")); if ( l && l.thelamp ) CompanionLamp(l.thelamp).justteleport = true; } States { Spawn: // normal idle #### # 2; XZW1 A 1 { if ( !player || (player.mo != self) ) return ResolveState("VoodooSpawn"); return A_JumpIf(player&&(player.mo==self)&&(abs(player.cmd.yaw|player.cmd.pitch)>128),"Turn"); } Wait; See: // normal walking #### # 2; XZW1 BCD 2 A_JumpIf(FastCheck(false),"SeeFast"); XZW1 E 0 A_Footstep(1); XZW1 EFGHIJKL 2 A_JumpIf(FastCheck(false),"SeeFast"); XZW1 M 0 A_Footstep(-1); XZW1 MNOPA 2 A_JumpIf(FastCheck(false),"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; SeeFast: // sprinting #### # 2 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.3); XZW1 QRST 2; Goto SeeFastLoop; SeeFastLoop: // keep sprinting XZW1 U 0 A_Footstep(1,true); XZW1 UVWXYZ 2 A_JumpIf(FastCheck(true),"SeeFastEnd"); XZW2 A 2 A_JumpIf(FastCheck(true),"SeeFastEnd"); XZW2 B 0 A_Footstep(-1,true); XZW2 BCDEFG 2 A_JumpIf(FastCheck(true),"SeeFastEnd"); XZW1 T 2 A_JumpIf(FastCheck(true),"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 { if ( !player || (player.mo != self) ) return ResolveState("VoodooPain"); return 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 { if ( !player || (player.mo != self) ) return ResolveState("VoodooDeath"); return A_JumpIf(player&&(player.mo==self)&&(player.crouchdir==-1),"CrouchDeath"); } XZW1 A 2 { A_DemoScream(); A_NoBlocking(); } XZW2 RSTUVWXYZ 2; XZW3 ABCDEFG 2; XZW3 H 1 A_DMFade(); Wait; Jump: // start boost #### # 2; XZW3 IJKLMNO 2 { if ( player.onground||bNoGravity||(waterlevel>=3) ) return ResolveState("JumpEnd"); A_BoostUp(true); return ResolveState(null); } // keep boost XZW3 P 1 { if ( player.onground||bNoGravity||(waterlevel>=3) ) return ResolveState("JumpEnd"); A_BoostUp(false); return ResolveState(null); } XZW3 P 1 A_JumpIf((vel.z<-10)&&(pos.z>(floorz+80)),"Fall"); Goto Jump+8; JumpEnd: // stop boost #### # 2; XZW3 PQRSTUVW 2; Goto Spawn+1; Fall: // start fall #### # 4; XZW3 XYZ 2 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd"); XZW4 AB 2 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"FallEnd"); Goto FallLoop; FallLoop: // falling XZW4 CDEFGH 3 A_JumpIf(player.onground&&!bNoGravity&&(waterlevel<3),"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 NOPQRSTUVWXYZ 3; XZW6 ABCD 3; Goto Spawn+1; Victory: #### # 3; XZW6 EFGHIJKLMNOPQRSTUVW 3; Goto Spawn+1; BlowKiss: #### # 3; XZWD EFGHIJKLMNOPQRSTUVW 3; Goto Spawn+1; Headpat: #### # 3; XZWH ST 3; XZWH UVW 2; XZWH XYZ 1; XZWI A 1; XZWI BCDE 2; XZWI FGH 1; XZWH XYZ 1; XZWI A 1; XZWI BCDE 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 FH 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; 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: XZW7 MN 2; XZW7 O 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2); XZW7 OPQRS 2; XZW7 T 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2); XZW7 TUV 2; Loop; CrouchWave: #### # 3; XZWF ABCDEFGHIJKLMNO 3; Goto Crouch+1; CrouchApprove: #### # 3; XZWF PQRSTUVWXYZ 3; XZWG ABCDEF 3; Goto Crouch+1; CrouchVictory: #### # 3; XZWG GHIJKLMNOPQRSTUVWXY 3; Goto Crouch+1; CrouchBlowKiss: #### # 3; XZWG Z 3; XZWH ABCDEFGHIJKLMNOPQR 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; 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(); A_NoBlocking(); } 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: XZWE MNO 2; XZWE P 0 A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2); XZWE PQRSTU 2; XZWE V 0 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.2); XZWE VWX 2; Loop; SwimEnd: #### # 2; XZWE MYZ 2; Goto Float+1; VoodooSpawn: XZWZ A -1; Loop; VoodooPain: XZWZ A 1; XZWZ B 1 A_DemoPain(); XZWZ CDEF 1; Goto VoodooSpawn; VoodooDeath: XZWZ A 2 { A_DemoScream(); A_NoBlocking(); } XZWZ GHIJKLMNO 2; XZWZ PQR 2; XZWZ S -1; Stop; } } Class DashTrail : Actor { Default { RenderStyle "Add"; Radius 2; Height 2; Scale 0.3; +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOTELEPORT; +NOINTERACTION; FloatBobPhase 0; } override void PostBeginPlay() { Super.PostBeginPlay(); SetState(FindState("Spawn")+Random[ExploS](0,7)); let t = Spawn("DashTrail2",level.Vec3Offset(pos,vel*.3)); t.target = target; t.vel = vel*1.2; let s = Spawn("SWWMSmoke",level.Vec3Offset(pos,vel*1.6)); s.vel = vel*.8; s.SetShade(Color(1,1,1)*Random[ExploS](64,128)); s.special1 = Random[ExploS](2,4); s.scale *= 1.4; s.alpha *= .3; } override void Tick() { Super.Tick(); // hack if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION); } States { Spawn: JFLB ABCDEFGH 1 Bright { A_FadeOut(.2); A_SetScale(scale.x*.95); } Loop; } } Class DashTrail2 : Actor { Default { RenderStyle "Add"; Radius 2; Height 2; Scale 0.2; Alpha 0.4; +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOTELEPORT; +NOINTERACTION; FloatBobPhase 0; } override void PostBeginPlay() { Super.PostBeginPlay(); SetState(FindState("Spawn")+Random[ExploS](0,7)); } override void Tick() { Super.Tick(); // hack if ( target && (players[consoleplayer].Camera == target) ) Warp(target,pos.x,pos.y,pos.z,0,WARPF_ABSOLUTEPOSITION|WARPF_COPYINTERPOLATION); } States { Spawn: JFLR ABCDEFGH 1 Bright { A_FadeOut(.02); A_SetScale(scale.x*1.04); if ( waterlevel > 0 ) { let b = Spawn("SWWMBubble",pos); b.vel = vel; b.scale *= scale.x; Destroy(); } } Loop; } } Class DemolitionistRadiusShockwaveTail : Actor { Default { RenderStyle "Add"; Radius 16; Height 8; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +NOTELEPORT; +NOINTERACTION; } States { Spawn: XZW1 A 1 { pitch = min(85,(pitch+2)*1.05); A_FadeOut(.02); A_SetScale(scale.x*1.08,scale.y); vel *= .98; } Wait; } } Class DemolitionistRadiusShockwave : Actor { Actor lasthit; Default { RenderStyle "Add"; Speed 15; DamageFunction int(200*alpha); DamageType "GroundPound"; Radius 16; Height 8; Alpha .4; XScale .65; YScale 3.; PROJECTILE; +DONTSPLASH; +STEPMISSILE; +NOEXPLODEFLOOR; +FLATSPRITE; +RIPPER; +BLOODLESSIMPACT; -NOGRAVITY; } override int DoSpecialDamage( Actor target, int damage, Name damagetype ) { if ( target == lasthit ) return 0; lasthit = target; if ( damage <= 0 ) return damage; if ( (target.mass < LARGE_MASS) && !target.bDONTTHRUST ) { target.vel.xy += vel.xy.unit()*(30000./max(50,target.mass))*alpha; if ( (target.pos.z <= floorz) || !target.TestMobjZ() ) target.vel.z += (4000./max(50,target.mass))*alpha; } return damage; } States { Spawn: XZW1 A 1 { SetZ(floorz); pitch = min(85,(pitch+2)*1.05); if ( !Random[ExploS](0,3) ) Spawn("InvisibleSplasher",Vec3Offset(0,0,2)); let s = Spawn("DemolitionistRadiusShockwaveTail",pos); s.vel = vel*.35; s.scale = scale; s.alpha = alpha*.4; s.angle = angle; s.pitch = pitch; s.roll = roll; A_FadeOut(.015); A_SetScale(scale.x*1.08,scale.y); vel *= .98; } Wait; Death: XZW1 A 1 { SetZ(floorz); A_FadeOut(.05); A_SetScale(scale.x*1.1,scale.y*.97); } Wait; } } Class DemolitionistShockwave : Actor { Default { +NOGRAVITY; +NOBLOCKMAP; +NOTELEPORT; +NODAMAGETHRUST; +FORCERADIUSDMG; +NOINTERACTION; } override void PostBeginPlay() { Super.PostBeginPlay(); A_QuakeEx(7,7,7,30,0,300+min(special1,50)*4,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:1.5); if ( target.player != players[consoleplayer] ) { A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.3); A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.2,pitch:.7); A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.1,pitch:.4); } SWWMUtility.DoExplosion(self,40+min(special1,120),100000+min(special1*2000,150000),100+min(special1*2,130),80,DE_BLAST|DE_EXTRAZTHRUST,'GroundPound',target); for ( int i=0; i<360; i+=5 ) { Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3); let s = Spawn("SWWMSmoke",Vec3Angle(4,i,8)); s.vel = pvel+(cos(i),sin(i),0)*7.; s.SetShade(Color(1,1,1)*Random[ExploS](64,224)); s.special1 = Random[ExploS](1,4); s.scale *= 1.5; s.alpha *= .4; } if ( pos.z > floorz+16 ) return; for ( int i=0; i<360; i+=5 ) { let r = Spawn("DemolitionistRadiusShockwave",Vec3Angle(5,i)); r.target = target; r.angle = i; r.vel.xy = (cos(i),sin(i))*(r.speed+min(special1*.15,30)); r.alpha *= .1+min(special1*.03,.9); } int numpt = Random[ExploS](10,20); for ( int i=0; i 0) && (--EffectTics == 0)) ) Destroy (); } override void InitEffect() { Super.InitEffect(); // adjust the duration EffectTics = max(0,swwm_revivecooldown)*Thinker.TICRATE; } override void EndEffect() { Super.EndEffect(); if ( !Owner ) return; Owner.A_StartSound("demolitionist/revive",CHAN_ITEMEXTRA); if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_REFAIL")); } override void OwnerDied() { // do nothing, this "powerup" is preserved on death } } // not an actual light, just handles the attach/detach Class DemolitionistSelfLight : Actor { bool oldactive; bool oldglow; Default { +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOINTERACTION; FloatBobPhase 0; } bool activelight() { // active all the time except when invisible or in certain // animation frames if ( target.bINVISIBLE || (target.alpha <= double.epsilon) ) return false; if ( target.InStateSequence(target.CurState,target.FindState("Death")) && ((target.frame == 20) || (target.frame == 22) || (target.frame == 23) || (target.frame == 25) || (target.frame == 27) || (target.frame == 28) || (target.frame == 30) || (target.frame == 31) || (target.frame == 32) || (target.frame == 33) || (target.frame == 34)) ) return false; if ( target.InStateSequence(target.CurState,target.FindState("CrouchDeath")) && ((target.frame == 7) || (target.frame == 10) || (target.frame == 11)) ) return false; return true; } override void Tick() { if ( !target || !(target is 'Demolitionist') || (Demolitionist(target).selflight != self) ) { Destroy(); return; } bool curactive = activelight(); if ( curactive && !oldactive ) target.A_AttachLight('DemoSelfLight',DynamicLight.PointLight,Color(56,72,88),200,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE|DynamicLight.LF_SPOT,(5,0,target.player?(target.player.viewz-target.pos.z):(target.height*.93)),0,30,90,target.pitch); else if ( !curactive && oldactive ) target.A_RemoveLight('DemoSelfLight'); oldactive = curactive; bool curglow = !(target.bINVISIBLE||(target.alpha <= double.epsilon)); if ( curglow && !oldglow ) target.A_AttachLight('DemoSelfLight2',DynamicLight.PointLight,Color(32,48,24),80,0,DynamicLight.LF_DONTLIGHTSELF|DynamicLight.LF_ATTENUATE,(0,0,target.height/2)); else if ( !curglow && oldglow ) target.A_RemoveLight('DemoSelfLight2'); oldglow = curglow; } } // 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; } } Class LoveHeartTrail : Actor { Default { RenderStyle "Add"; Radius .1; Height 0.; Alpha .1; +NOGRAVITY; +NOBLOCKMAP; +NOINTERACTION; +DONTSPLASH; +NOTELEPORT; +FORCEXYBILLBOARD; } override void Tick() { if ( isFrozen() ) return; A_FadeOut(.01); scale *= .95; } States { Spawn: DOKI A -1 Bright; Stop; } } Class LoveHeartSparkle : Actor { Default { Radius .1; Height 0.; Scale .03; +NOGRAVITY; +NOBLOCKMAP; +NOINTERACTION; +DONTSPLASH; +NOTELEPORT; +FORCEXYBILLBOARD; } override void PostBeginPlay() { Scale *= FRandom[ExploS](.75,1.5); specialf1 = FRandom[ExploS](.95,.98); specialf2 = FRandom[ExploS](.01,.03); vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch))*FRandom[ExploS](2,8); } override void Tick() { if ( isFrozen() ) return; A_SetScale(scale.x*specialf1); A_FadeOut(specialf2); Vector3 dir = vel; double magvel = dir.length(); magvel *= .99; if ( magvel > 0. ) { dir /= magvel; dir += .2*(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)); vel = dir.unit()*magvel; } SetOrigin(level.Vec3Offset(pos,vel),true); } States { Spawn: DOKI A -1 Bright; Stop; } } Class LoveHeartBurstLight : PaletteLight { Default { Tag "LovePal"; ReactionTime 15; Args 0,0,0,150; } } Class LoveHeartLight : PointLightAttenuated { Default { Args 255,176,208,80; } override void Tick() { Super.Tick(); if ( target || target.InStateSequence(target.CurState,target.FindState("Death")) ) { Destroy(); return; } SetOrigin(target.pos,true); } } Class LoveHeart : Actor { Default { Obituary "$O_DOKIDOKI"; DamageType 'Love'; DamageFunction (clamp(special2,5,15)); Radius 4; Height 4; Speed 10; Scale .2; PROJECTILE; +BLOODLESSIMPACT; +FORCEXYBILLBOARD; +SEEKERMISSILE; +FOILINVUL; +PAINLESS; +NODAMAGETHRUST; } override int DoSpecialDamage( Actor target, int damage, Name damagetype ) { let raging = RagekitPower(self.target.FindInventory("RagekitPower")); if ( raging ) { bEXTREMEDEATH = true; bNOEXTREMEDEATH = false; } else { bEXTREMEDEATH = false; bNOEXTREMEDEATH = true; } if ( target is 'WolfensteinSS' ) target.bFRIENDLY = false; if ( target.IsFriend(self.target) || (target is 'MBFHelperDog') ) { int healamt = clamp(special2,5,15); if ( raging ) { healamt *= 8; raging.DoHitFX(); } if ( target.GiveBody(healamt,target.GetSpawnHealth()) ) { SWWMScoreObj.Spawn(healamt,target.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+target.Height/2),Font.CR_BLUE); SWWMHandler.DoFlash(target,Color(32,224,128,255),10); } if ( target is 'MBFHelperDog' ) { // befriend good doggo if ( target.bCOUNTKILL ) { target.bCOUNTKILL = false; level.total_monsters--; } target.bFRIENDLY = true; if ( deathmatch ) target.SetFriendPlayer(self.target.player); } return 0; } Vector3 dirto = level.Vec3Diff(pos,target.Vec3Offset(0,0,target.Height/2)).unit(); SWWMUtility.DoKnockback(target,dirto,1500.*damage); let bread = target.FindState("Pain"); if ( bread ) target.SetState(bread); if ( raging ) { damage *= 8; raging.DoHitFX(); } if ( target is 'WolfensteinSS' ) { damage = int.max; bEXTREMEDEATH = true; bNOEXTREMEDEATH = false; } else if ( target is 'SWWMHangingKeen' ) damage = max(target.Health,damage); // rescued by love :3 else if ( SWWMHDoomHandler.IsCuteGirl(target) ) { // no cute demon girl can resist demo's charm damage = max(target.Health,damage); bEXTREMEDEATH = false; bNOEXTREMEDEATH = true; } return damage; } override int SpecialMissileHit( Actor victim ) { if ( !victim.bSHOOTABLE && (victim != tracer) ) return 1; if ( tracer && (victim != tracer) ) return 1; return -1; } action void A_HeartTick() { special1++; if ( !(special1%3) && (special2 > 0) ) special2--; A_SetScale(.2+.02*sin(special1*.25*TICRATE)); double magvel = vel.length(); if ( magvel > 0 ) { Vector3 dir = vel/magvel; vel = dir*min(30,magvel*1.1); } double steppy = vel.length()/4.; for ( int i=2; i<6; i++ ) { Vector3 dir2 = vel.unit(); let t = Spawn("LoveHeartTrail",level.Vec3Offset(pos,-dir2*steppy*i)); t.scale = scale; } int numpt = Random[ExploS](1,3); for ( int i=0; i PlayerPawn(user.player.mo).UseRange ) return false; if ( user is 'Demolitionist' ) { patter = user; let g = SWWMGesture.SetGesture(Demolitionist(patter),GS_Headpat); if ( !g ) return false; // can't headpat at the moment patting = true; g.pats = self; oldtargettics = target.tics; target.tics = -1; patter.player.cheats |= CF_TOTALLYFROZEN; Demolitionist(patter).scriptedinvul = true; target.bDORMANT = true; return true; } return false; } } Class HHitList { Actor a; Vector3 dir; } enum EGestureSlot { // special use GS_Headpat = -50, GS_Grenade, // no gesture GS_Null = 0, // general gestures GS_Wave = 1, GS_ThumbsUp, GS_Victory, GS_BlowKiss }; // First person gestures Class SWWMGesture : SWWMWeapon { Weapon formerweapon; int whichgesture, nextgesture; bool deaded, queued; State whichstate; Actor whichcaller; Array sstate; Array scaller; int gonect; HeadpatTracker pats; // for headpat gesture, our current tracker // these should prevent autoswitch when out of ammo override bool ReportHUDAmmo() { return false; } override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount ) { return false; } override void DoEffect() { Super.DoEffect(); if ( !Owner || !Owner.player || (Owner.player.ReadyWeapon != self) ) return; let psp = Owner.player.FindPSprite(PSP_WEAPON); if ( !psp ) return; if ( (Owner.Health <= 0) && (psp.CurState != ResolveState("Deselect")) ) Owner.player.SetPSprite(PSP_WEAPON,ResolveState("Deselect")); } static SWWMGesture SetGesture( PlayerPawn mo, int which ) { if ( !mo || !(mo is 'Demolitionist') ) return null; // only Demo if ( mo.Health <= 0 ) return null; // dead if ( mo.player.cheats&CF_TOTALLYFROZEN ) return null; // frozen today SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture")); if ( (mo.player.PendingWeapon == w) || (mo.player.ReadyWeapon == w) ) { // already gesturing // just queue another one if ( which <= 0 ) return null; // these gestures can't be queued else { w.nextgesture = which; w.queued = true; } return null; } if ( !w ) { w = SWWMGesture(Spawn("SWWMGesture")); mo.AddInventory(w); } if ( mo.player.PendingWeapon != WP_NOCHANGE ) w.formerweapon = mo.player.PendingWeapon; else w.formerweapon = mo.player.ReadyWeapon; w.whichstate = null; w.whichgesture = which; mo.player.PendingWeapon = w; return w; } // "special" gestures go to a specified state, which may be external static SWWMGesture SetSpecialGesture( PlayerPawn mo, Actor a, bool pickup = false ) { if ( !mo || !(mo is 'Demolitionist') ) return null; // only Demo if ( mo.Health <= 0 ) return null; // dead if ( mo.player.cheats&CF_TOTALLYFROZEN ) return null; // frozen today State which = null; if ( pickup ) which = a.FindState("PickupGesture"); else which = a.FindState("UseGesture"); if ( !which ) return null; // state not found SWWMGesture w = SWWMGesture(mo.FindInventory("SWWMGesture")); if ( (mo.player.PendingWeapon == w) || (mo.player.ReadyWeapon == w) ) { // already gesturing // queue if unique if ( w.sstate.Find(which) != w.sstate.Size() ) { w.sstate.Push(which); w.scaller.Push(a); } return null; } if ( !w ) { w = SWWMGesture(Spawn("SWWMGesture")); mo.AddInventory(w); } if ( mo.player.PendingWeapon != WP_NOCHANGE ) w.formerweapon = mo.player.PendingWeapon; else w.formerweapon = mo.player.ReadyWeapon; w.whichgesture = GS_Null; w.whichstate = which; w.whichcaller = a; mo.player.PendingWeapon = w; return w; } action void A_CallPlayerGesture( statelabel st, statelabel cst ) { if ( invoker.Owner.Health <= 0 ) return; if ( (player.crouchdir == -1) && invoker.Owner.FindState(cst) ) invoker.Owner.SetStateLabel(cst); else if ( invoker.Owner.FindState(st) ) invoker.Owner.SetStateLabel(st); } action void A_FinishGesture() { if ( invoker.sstate.Size() > 0 ) { invoker.whichgesture = GS_Null; invoker.whichstate = invoker.sstate[0]; invoker.whichcaller = invoker.scaller[0]; // push back invoker.sstate.Delete(0); invoker.scaller.Delete(0); player.SetPSprite(PSP_WEAPON,ResolveState("Ready")); return; } if ( invoker.queued ) { invoker.whichstate = null; invoker.whichgesture = invoker.nextgesture; invoker.queued = false; player.SetPSprite(PSP_WEAPON,ResolveState("Ready")); return; } player.PendingWeapon = invoker.formerweapon; player.SetPSprite(PSP_WEAPON,ResolveState("Deselect")); } action void A_Headpat() { A_StartSound("demolitionist/petting",CHAN_WEAPON,CHANF_OVERLAP,.4); let pt = invoker.pats; if ( !pt ) return; int numpt = Random[ExploS](6,9); Vector3 dir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); Vector3 patpos = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz-4),dir*30.); for ( int i=0; i hits; hits.Clear(); int rings = 1; FLineTraceData d; for ( double i=0; i<.2; i+=.02 ) { for ( int j=0; j<360; j+=(360/rings) ) { dir = (x2+y2*cos(j)*i+z2*sin(j)*i).unit(); LineTrace(atan2(dir.y,dir.x),8000.,asin(-dir.z),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); if ( d.HitType != TRACE_HitActor ) continue; bool addme = true; for ( int k=0; k