swwmgz_m/zscript/player/swwm_player_tick.zsc

843 lines
28 KiB
Text

// 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<Inventory> 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<CurSector.Get3DFloorCount(); i++ )
{
let ff = CurSector.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || !(ff.flags&F3DFloor.FF_SWIMMABLE) ) continue;
double ff_bottom = ff.bottom.ZAtPoint(pos.xy);
double ff_top = ff.top.ZAtPoint(pos.xy);
if ( (ff_top <= pos.z) || (ff_bottom > 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<numpt; i++ )
{
let p = Spawn("SWWMBubble",Vec3Offset(FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](-radius,radius)*.8,FRandom[ExploS](height*.1,height*.9)));
p.scale *= FRandom[ExploS](.02,.2);
p.vel += vel*.2;
}
}
CheckUnderwaterAmb();
SenseItems();
CheckItemMagnet();
if ( vel.length() > 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<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 fnorm;
if ( ff ) fnorm = -ff.top.Normal;
else fnorm = FloorSector.floorplane.Normal;
double maxsteep = clamp(abs(lastvelz)*.1,0.,.9);
if ( fnorm.z < maxsteep ) vel = .8*fnorm*(-lastvelz)*(1.-.8*fnorm.z);
}
}
// 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) )
{
if ( (encroached is 'FroggyChair') && (encroached != oldencroached) )
encroached.A_StartSound("squeak",CHAN_BODY,CHANF_OVERLAP);
// 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);
}
encroachtics++;
if ( !(encroachtics%GameTicRate) && (encroached.bISMONSTER || encroached.player || (encroached is 'ScriptedMarine')) && !IsFriend(encroached) )
SWWMUtility.AchievementProgress("step",encroachtics/GameTicRate,player);
}
else encroachtics = 0;
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;
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;
}
Vector3 temp = (ViewAngle,ViewPitch,ViewRoll);
if ( abs(bumpangle) > double.epsilon )
{
temp.x += bumpangle*.5;
bumpangle *= .8;
}
if ( abs(bumppitch) > double.epsilon )
{
temp.y += bumppitch*.5;
bumppitch *= .8;
}
if ( abs(bumproll) > double.epsilon )
{
temp.z += bumproll*.5;
bumproll *= .8;
}
// stabilize view angles
A_SetViewAngle(temp.x*.8,SPF_INTERPOLATE);
A_SetViewPitch(temp.y*.8,SPF_INTERPOLATE);
A_SetViewRoll(temp.z*.8,SPF_INTERPOLATE);
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<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 ceilnorm;
if ( ff ) ceilnorm = ff.model.floorplane.Normal;
else ceilnorm = CeilingSector.ceilingplane.Normal;
// don't bump if we're only grazing it
if ( dir dot ceilnorm < -.6 )
{
bool busted = false;
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;
bool buttslam = false;
// BUTTSLAM
if ( dir dot viewdir < -.3 )
{
dmg *= 3;
buttslam = true;
}
if ( ff ) tempme.Results.ffloor = ff;
tempme.Results.HitSector = CeilingSector;
tempme.Results.HitType = TRACE_HitCeiling;
if ( BusterWall.Bust(tempme.Results,dmg,self,dir,pos.z+Height) )
{
// busted through
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = buttslam?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
busted = true;
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(0,0,Height));
A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
mystats.buttslams++;
lastbump *= .8;
}
}
}
if ( !busted || !raging )
{
// headbump
bumped = true;
SWWMUtility.AchievementProgressInc("bonk",1,player);
A_StartSound("demolitionist/bump",CHAN_DAMAGE,CHANF_OVERLAP);
bumptic = gametic+int(20+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.z += 5+(spd*(10/mass));
vel += ceilnorm*(15+(spd*40/mass));
dashboost *= 0.;
if ( raging )
{
let ps = Spawn("BigPunchSplash",Vec3Offset(0,0,Height));
ps.damagetype = (dir dot viewdir < -3.)?'Buttslam':'Dash';
ps.target = self;
ps.special1 = int(10+spd*3.);
raging.DoHitFX();
}
}
}
}
// check for wall collision
FCheckPosition tm;
Vector2 steppy = dir.xy*spd/8.;
Vector3 oldpos = pos;
bool oldthru = bTHRUACTORS;
bTHRUACTORS = true;
for ( int i=1; i<=8; i++ )
{
if ( (spd > 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<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 fnorm;
if ( ff ) fnorm = -ff.top.Normal;
else fnorm = FloorSector.floorplane.Normal;
// how far up should the boost be
double xspd = spd*(1.-dir.z);
vel.z += xspd*(1.-fnorm.z);
}
SetOrigin(oldpos,true);
bTHRUACTORS = oldthru;
bCANPUSHWALLS = oldpush;
bCANUSEWALLS = olduse;
bACTIVATEMCROSS = oldmcross;
bNOTELEPORT = oldtele;
}
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.master = self;
a.vel = .5*(RotateVector((j,0),angle+i*160),0)-(0,0,1)*j;
a.vel -= vel*.5;
}
}
}
}