851 lines
29 KiB
Text
851 lines
29 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;
|
|
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<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;
|
|
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 ( !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();
|
|
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);
|
|
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) )
|
|
{
|
|
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(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
|
|
let [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*.5;
|
|
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
|
|
if ( temp dot temp < double.epsilon ) temp = (0,0,0);
|
|
else temp *= .8;
|
|
if ( ViewAngle != temp.x ) A_SetViewAngle(temp.x,SPF_INTERPOLATE);
|
|
// IMPORTANT: don't call A_SetViewPitch if the player's min/max pitch are not yet initialized
|
|
if ( (ViewPitch != temp.y) && !(player.MinPitch == player.MaxPitch == pitch) ) A_SetViewPitch(temp.y,SPF_INTERPOLATE);
|
|
if ( ViewRoll != temp.z ) A_SetViewRoll(temp.z,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] || !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<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;
|
|
}
|
|
}
|
|
}
|
|
}
|