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