// Tick and friends extend Class Demolitionist { void SenseItems() { if ( player.cmd.buttons&BT_USER3 ) { let bt = BlockThingsIterator.Create(self,800); foreach ( i,p,f:bt ) { if ( !i ) continue; if ( !(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); foreach ( t,p,f:bt ) { if ( !t ) continue; if ( !(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; i.bDROPOFF = true; i.bSLIDESONWALLS = true; i.bNOBLOCKMONST = 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; double PrevHeight = Height; Super.Tick(); double HeightDiff = Height-PrevHeight; // shift vertically around center of gravity if crouching while airborne (crouch jumping) if ( player && (player.mo == self) && CanCrouch() && (player.playerstate != PST_DEAD) && !player.onground && (abs(HeightDiff) > 0) ) { double oldz = pos.z; SetZ(pos.z-HeightDiff*.5); // note: even with this check, clipping through sloped 3D floors may still happen // (honestly, sloped 3d floors are a source of many troubles) if ( !TryMove(pos.xy,false) ) SetZ(oldz); } if ( (gamestate != GS_LEVEL) || !player || (player.mo != self) || (freezetics > 0) ) return; isAnalogMoving = CVar.GetCVar('swwm_analogmove',player).GetBool(); UpdateFace(); UpdateTags(); if ( !hnd ) hnd = SWWMHandler(EventHandler.Find('SWWMHandler')); if ( hnd ) { if ( hasteleported ) hnd.SendInterfaceEvent(PlayerNumber(),"swwmsendplayertele"); else hnd.SendInterfaceEvent(PlayerNumber(),"swwmsendplayerstep"); } 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; if ( vel dot vel > lagvel dot lagvel ) lagvel = lagvel*.8+vel*.2; else lagvel = lagvel*.4+vel*.6; double traveldist = level.Vec3Diff(oldpos,pos).length(); if ( (traveldist == double.infinity) || (traveldist != traveldist) ) traveldist = 0.; // prevent glitches from breaking stats 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 0) || GetFloorTerrain().isliquid ) { if ( footblood[0] > 0. ) footblood[0] = max(0.,footblood[0]*.95-.05); if ( footblood[1] > 0. ) footblood[1] = max(0.,footblood[1]*.95-.05); } CheckUnderwaterAmb(); SenseItems(); CheckItemMagnet(); double spd = vel.length(); if ( spd > mystats.topspeed ) mystats.topspeed = spd; if ( spd > ((3600*GameTicRate)/32000.) ) SWWMUtility.AchievementProgress("sanic",int((spd*3600*GameTicRate)/32000.),player); bool isdashing = InStateSequence(CurState,FindState('Dash')); 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 ( !IsRunning() || (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); } // also cancel dashing if we stomped if ( isdashing ) dashboost = 0.; mystats.stompcount++; } if ( lastvelz < landingspeed ) A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP); if ( (lastvelz < -gruntspeed) && (swwm_mutevoice < 4) && (health > 0) ) { String myvoice = CVar.GetCVar('swwm_voicetype',player).GetString(); int loudlv = swwm_voiceamp; int maxgrunt = StringTable.Localize("$SWWM_"..myvoice.."_NGRUNT").ToInt(); int idx = (maxgrunt<=1)?1:Random[DemoLines](1,maxgrunt); A_StartSound(String.Format("voice/%s/grunt%d",myvoice,idx),CHAN_DEMOVOICE,CHANF_OVERLAP); if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/grunt%d",myvoice,idx),CHAN_DEMOVOICEAUX,CHANF_OVERLAP); if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/grunt%d",myvoice,idx),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP); if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/grunt%d",myvoice,idx),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP); } if ( lastvelz < -1 ) { PlayFootstep(-1,1,clamp(-lastvelz*0.05,0.0,1.0),true); PlayFootstep(1,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); } } } double bstr = CVar.GetCVar('swwm_bumpstrength',player).GetFloat(); if ( abs(bumpvelz) > double.epsilon ) { lagvel.z += bumpvelz*bstr*.5; bumpvelz *= .8; } Vector3 temp = (ViewAngle,ViewPitch,ViewRoll); if ( abs(bumpangle) > double.epsilon ) { temp.x += bumpangle*bstr*.5; bumpangle *= .8; } if ( abs(bumppitch) > double.epsilon ) { temp.y += bumppitch*bstr*.5; bumppitch *= .8; } if ( abs(bumproll) > double.epsilon ) { temp.z += bumproll*bstr*.5; bumproll *= .8; } // stabilize view angles if ( temp dot temp < double.epsilon ) temp = (0,0,0); else temp *= .8; // we have to bypass the A_SetView* functions due to a bug that // will likely never get addressed if ( ViewAngle != temp.x ) { ViewAngle = temp.x; player.cheats |= CF_INTERPVIEWANGLES; } if ( ViewPitch != temp.y ) { ViewPitch = temp.y; player.cheats |= CF_INTERPVIEWANGLES; } if ( ViewRoll != temp.z ) { ViewRoll = temp.z; 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 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 raging = RagekitPower(FindInventory('RagekitPower')); double maxmass = max(mass*spd/40.,200); if ( raging ) maxmass *= 2; let bt = BlockThingsIterator.Create(self,spd+radius+1024); foreach( a,p,f:bt ) { if ( spd <= 0 ) break; // if something stopped us, no more iteration 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(); } } bt.Destroy(); // 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] || !Level.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; } } } }