// 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; 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; int deadtimer; transient int revivefail; Default { Speed 1; Radius 16; 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.0; DamageFactor "Poison", 0.0; DamageFactor "PoisonCloud", 0.0; DamageFactor "Falling", 0.0; +NOBLOOD; +DONTGIB; +NOICEDEATH; } // 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 = 1000; } 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 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 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 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; 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 ) 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 == 'GroundPound' ) return StringTable.Localize("$O_POUND"); return Super.GetObituary(victim,inflictor,mod,playerattack); } override void GiveDefaultInventory() { Super.GiveDefaultInventory(); // preloaded gun let dp = DeepImpact(FindInventory("DeepImpact")); if ( dp ) SWWMLoreLibrary.Add(player,"DeepImpact"); let eg = ExplodiumGun(FindInventory("ExplodiumGun")); if ( !eg ) return; SWWMLoreLibrary.Add(player,"ExplodiumGun"); eg.clipcount = 7; eg.chambered = true; } override void PostBeginPlay() { Super.PostBeginPlay(); mystats = SWWMStats.Find(player); lastground = true; } void A_Dash() { vel += dashdir*dashboost; player.vel = vel.xy; if ( dashboost < 0.1 ) dashboost = 0.; else { A_AlertMonsters(800); dashboost *= .5; } mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost); dashfuel = max(0.,dashfuel-dashboost); dashcooldown = 40; fuelcooldown = max(60,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 < 0.1 ) dashboost = 0.; else { A_AlertMonsters(800); dashboost *= (player.cmd.buttons&BT_JUMP)?.9:.4; last_boost = level.maptime+1; } mystats.fuelusage += dashfuel-max(0.,dashfuel-dashboost); 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 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 ) // 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; } } 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_LOOPING|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 ) { 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; } cur.x += diffang; cur.y -= diffpitch; 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; } 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; } override void Tick() { Super.Tick(); if ( !player ) return; 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(prev,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; } } CheckUnderwaterAmb(); 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) && (health > 0) ) { // bump down weapon bumpdown = true; bumpvelz = -lastvelz; if ( lastvelz < -30 ) { let s = Spawn("DemolitionistShockwave",pos); s.target = self; s.special1 = int(-lastvelz); ReactionTime = 17; A_Stop(); A_AlertMonsters(); 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) ) 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)); } if ( bumpdown ) { lagvel.z += bumpvelz*.2; bumpvelz *= .8; if ( bumpvelz < double.epsilon ) bumpdown = false; } lastground = player.onground; lastvelz = prevvelz; prevvelz = vel.z; bNOFRICTION = (((waterlevel<2)&&(bFly||bFlyCheat&&!(player.cheats&CF_NOCLIP2)))||InStateSequence(CurState,FindState("Dash"))); fuelcooldown = max(0,fuelcooldown-1); dashcooldown = max(0,dashcooldown-1); boostcooldown = max(0,boostcooldown-1); if ( fuelcooldown <= 0 ) dashfuel = min(120.,dashfuel+max(dashfuel*.01,.05)); if ( (dashboost > 0.) && (InStateSequence(CurState,FindState("Dash")) || (InStateSequence(CurState,FindState("Jump")) && player.cmd.buttons&BT_JUMP)) ) dashsnd = true; else { if ( dashsnd ) A_StartSound("demolitionist/jetstop",CHAN_JETPACK); dashsnd = false; } PainChance = InStateSequence(CurState,FindState("Dash"))?0:255; if ( dashboost <= 0. ) return; if ( InStateSequence(CurState,FindState("Dash")) ) { Actor a; 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+dashdir*dashboost; double spd = dir.length(); dir = dir.unit(); // look for things we could potentially bump into let bi = BlockThingsIterator.Create(self,200); bool bumped = false; while ( (spd > 0) && bi.Next() ) { a = bi.Thing; if ( !a || (a == self) || !a.bSHOOTABLE || a.bTHRUACTORS ) continue; Vector3 diff = level.Vec3Diff(level.Vec3Offset(pos,vel),level.Vec3Offset(a.pos,a.vel)); if ( (abs(diff.x) > (radius+a.radius+8)) || (abs(diff.y) > (radius+a.radius+8)) ) continue; if ( (diff.z > (height+8)) || (diff.z < (8-a.height)) ) continue; double moveang = atan2(dir.y,dir.x); Vector3 sc = level.SphericalCoords(pos,a.pos,(moveang,0)); if ( abs(sc.x) > 60 ) continue; // large monsters will stop the player A_QuakeEx(1,1,1,3,0,128,"",QF_RELATIVE|QF_SCALEDOWN); a.A_StartSound("demolitionist/bump",CHAN_FOOTSTEP,CHANF_OVERLAP); if ( a.bDONTTHRUST || (a.Mass >= Mass*5) ) { if ( bumped ) continue; bumped = true; A_QuakeEx(3,3,3,8,0,128,"",QF_RELATIVE|QF_SCALEDOWN); A_StartSound("demolitionist/bump",CHAN_FOOTSTEP,CHANF_OVERLAP); vel -= dir*(9+(spd*30/mass)); vel.z += 3+(spd*(20/mass)); } Vector3 pushdir = Vec3To(a).unit()*.1+dir*.9; if ( !a.bDONTTHRUST && (a.Mass < Mass*5) ) { a.vel += pushdir*(15+(spd*150/max(50,a.mass))); a.vel.z += 5+(spd*(50/max(50,a.mass))); } a.DamageMobj(self,self,int(15+spd*2.5),'Dash',DMG_THRUSTLESS); } } else if ( InStateSequence(CurState,FindState("Jump")) ) { 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 ) { // lucky collar if ( Health < 25 ) damage /= 4; if ( source == self ) damage /= 2; 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(); } 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 CheckCrouch( bool totallyfrozen ) { // crouch to swim/float down if ( !totallyfrozen && (player.cmd.buttons&BT_CROUCH) ) { double mult = (player.cmd.buttons&BT_SPEED)?2.:1.; if ( waterlevel >= 2 ) vel.z = -4*Speed*mult; else if ( bNOGRAVITY ) vel.z = -3*mult; } Super.CheckCrouch(totallyfrozen); } 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 ( InStateSequence(CurState,FindState("Dash")) ) 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',players[consoleplayer]); 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(); angle = nangle; pitch = npitch; roll = nroll; } else { angle += guideangle; pitch += guidepitch; pitch = clamp(pitch,player.MinPitch,player.MaxPitch); roll += guideroll; } 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 ( player.cmd.buttons&BT_SPEED ) jcmove *= 2.; 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/960.; double spd = vel.length(); if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd; else vel = vel+accel/TICRATE; } if ( abs(roll) > 0. ) roll += clamp(deltaangle(roll,0),-3.,3.); } 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) ) { ssup = max(0,(pos.z-floorz)); SetOrigin(Vec2OffsetZ(0,0,floorz),true); lastground = player.onground = true; } if ( player.onground ) lastgroundtic = gametic; 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.onground ) dodge.z = max(0,dodge.z); if ( dodge == (0,0,0) ) dodge.xy = RotateVector((1,0),angle); if ( (waterlevel < 2) && !(bFly || bFlyCheat) ) dodge.z += .15; if ( (dodge.length() > 0) && (dashcooldown <= 0) && (dashfuel > 20.) && player.cmd.buttons&BT_USER2 && level.IsJumpingAllowed() ) { dashdir = dodge.unit(); dashcooldown = 40; 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*mult; else if ( (waterlevel < 2) && (bFly || bFlyCheat) && !(player.cheats&CF_NOCLIP2) ) return; else if ( bNoGravity ) vel.z = 3*mult; 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; 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 ) { SWWMHandler.DoKnockback(jumpactor,(-walldir,0),12000); jumpactor.DamageMobj(self,self,10,'Jump'); } 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 pitch += dpitch; // add roll double droll = clamp(deltaangle(roll,50)*.5,-5,5); if ( abs(droll) < 2. ) roll = 50.; else roll += droll; player.mo.CalcHeight(); if ( player.damagecount ) player.damagecount--; if ( player.poisoncount ) player.poisoncount--; if ( player.viewheight <= 6 ) { deadtimer++; if ( deadtimer == 60 ) A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI|CHANF_LOCAL); if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) ) { // standard behaviour, respawn normally if ( (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) && swwm_revive ) { // reboot (if possible) if ( !FindInventory("ReviveCooldown") ) { player.Resurrect(); roll = 0; let s = Spawn("DemolitionistShockwave",pos); s.target = self; s.special1 = 30; ReactionTime = 17; A_Stop(); A_AlertMonsters(); 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); if ( special1 > 2 ) special1 = 0; if ( swwm_revivecooldown > 0 ) GiveInventory("ReviveCooldown",1); } else if ( level.maptime > revivefail ) { A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI|CHANF_LOCAL); revivefail = level.maptime+70; } } } 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("FloatLoop")) ) SetStateLabel("StartCrouch"); } else { if ( InStateSequence(CurState,FindState("Crouch")) || InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("EndCrouch"); else if ( InStateSequence(CurState,FindState("See")) || InStateSequence(CurState,FindState("Float")) || InStateSequence(CurState,FindState("FloatLoop")) ) { 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 ( !bNoGravity && (waterlevel < 1) ) { // Falling 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("FloatLoop"))) && (abs(pos.z-floorz) > maxstepheight) ) SetStateLabel("Fall"); } else { // Floating 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("FloatLoop")) ) SetStateLabel("Fall"); } } 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")) ) SetStateLabel("StartCrouch"); else if ( InStateSequence(CurState,FindState("Crouch")) ) SetStateLabel("CrouchMove"); } else { if ( InStateSequence(CurState,FindState("Crouch")) || InStateSequence(CurState,FindState("CrouchMove")) ) SetStateLabel("EndCrouch"); else if ( player.cmd.buttons&BT_SPEED && (InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) || InStateSequence(CurState,FindState("See"))) ) SetStateLabel("SeeFast"); else if ( InStateSequence(CurState,FindState("Spawn")) || InStateSequence(CurState,FindState("Turn")) ) { SetStateLabel("See"); A_StartSound("demolitionist/runstart",CHAN_FOOTSTEP,CHANF_OVERLAP,.2); } } } else if ( !bNoGravity && (waterlevel < 1) ) { // Falling PlayIdle(); } else { // Floating 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"); } } override void PlayAttacking() { 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 A_DMFade() { if ( !deathmatch || player ) return; A_FadeOut(0.03); } void A_DemoPain() { if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player); if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player); if ( lastdamage > 90 ) { A_StartSound("*pain25",CHAN_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("*pain50",CHAN_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("*pain100",CHAN_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); A_PlayerScream(); 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 = 500; // 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 = 5000; 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); 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') && mystats && !mystats.GotWeapon(Weapon(item).GetClass()) && CheckLocalView() ) SWWMHandler.AddOneliner("getweapon",2); if ( (item is 'Key') && !key_reentrant ) { // score int score = 250; Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score); SWWMCredits.Give(player,score); if ( !swwm_sharekeys ) return; // share all keys in mp for ( int i=0; i 0) ) { SWWMHandler.AddOneliner("greet",2); return true; } return false; } 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_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"SeeFast"); XZW1 E 0 A_Footstep(1); XZW1 EFGHIJKL 2 A_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"SeeFast"); XZW1 M 0 A_Footstep(-1); XZW1 MNOPA 2 A_JumpIf(player&&(player.cmd.buttons&BT_SPEED),"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(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd"); XZW2 A 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd"); XZW2 B 0 A_Footstep(-1,true); XZW2 BCDEFG 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"SeeFastEnd"); XZW1 T 2 A_JumpIf(player&&!(player.cmd.buttons&BT_SPEED),"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(); A_NoBlocking(); } XZW2 RSTUVWXYZ 2; XZW3 ABCDEFG 2; XZW3 H 350; 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; Float: // start #### # 2; XZW4 O 3; Goto FloatLoop; FloatLoop: XZW4 PQRS 6; Loop; FloatEnd: #### # 2; XZW4 TUVWX 3; 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; Taunt: #### # 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; Missile: Melee: // attacking XZW1 A 2; XZW6 XYZ 2; XZW7 ABC 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; CrouchMissile: XZW7 M 2; XZW7 WXYZ 2; XZW8 AB 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 350; XZW8 L 1 A_DMFade(); Wait; EndCrouch: #### # 2 A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP,.45); XZW8 MNOPQRS 2; Goto Spawn+1; } } Class DashTrail : Actor { Default { RenderStyle "Add"; Radius 2; Height 2; Scale 0.3; +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOTELEPORT; } 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; } 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 "Translucent"; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +WALLSPRITE; +NOTELEPORT; } States { Spawn: SDST A 1 { A_FadeOut(0.08); A_SetScale(scale.x,scale.y*0.98); } Wait; } } Class DemolitionistRadiusShockwave : Actor { Default { RenderStyle "Translucent"; Speed 10; DamageFunction int(20*alpha); DamageType "GroundPound"; Radius 16; Height 8; Alpha 0.8; Scale 1.2; +NOBLOCKMAP; +DONTSPLASH; +MISSILE; +STEPMISSILE; +NOEXPLODEFLOOR; +WALLSPRITE; +NOTELEPORT; +RIPPER; } override int DoSpecialDamage( Actor target, int damage, Name damagetype ) { if ( damage <= 0 ) return damage; if ( (target.mass < LARGE_MASS) && !target.bDONTTHRUST ) { target.vel.xy += vel.xy.unit()*(5000./max(50,target.mass))*alpha; target.vel.z += (800./max(50,target.mass))*alpha; } return damage; } States { Spawn: SDST A 1 { SetZ(floorz); Spawn("InvisibleSplasher",Vec3Offset(0,0,2)); let s = Spawn("DemolitionistRadiusShockwaveTail",pos); s.vel = vel*.8; s.scale = scale; s.alpha = alpha; s.angle = angle; A_FadeOut(.04); A_SetScale(scale.x*1.03,scale.y*0.99); } Wait; Death: SDST A 1 { SetZ(floorz); A_FadeOut(.1); A_SetScale(scale.x*1.06,scale.y*0.98); } Wait; } } Class DemolitionistShockwave : Actor { Default { +NOGRAVITY; +NOBLOCKMAP; +NOTELEPORT; +NODAMAGETHRUST; +FORCERADIUSDMG; } 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); } target.A_Blast(BF_AFFECTBOSSES,200+min(special1*3,100),70+min(special1*2,130),10+min(special1/10,20),"SWWMNothing",""); A_Explode(10+min(special1,90),70+min(special1*2,130),XF_EXPLICITDAMAGETYPE,damagetype:'GroundPound'); 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; r.alpha *= .1+min(special1*.03,.9); } 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; } 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 } }