Fix the incorrect SPAC_PCross behaviour in other spots. Redo swap weapon implementation. Should now theoretically allow for not just two, but many more swap weapons per slot. Minor fixups.
738 lines
22 KiB
Text
738 lines
22 KiB
Text
// Tach-Engine Technologies Microfusion Rotary Hammer aka "Pusher" (planned for unreleased Zanaveth Ultra Suite 2)
|
|
// Slot 1, replaces Chainsaw, Gauntlets, Timon's Axe
|
|
|
|
Class PusherImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_QuakeEx(2,2,2,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.3);
|
|
A_StartSound("pusher/hit",CHAN_VOICE);
|
|
A_SprayDecal("WallCrack",-20);
|
|
int numpt = Random[Pusher](4,8);
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Pusher](-.8,.8),FRandom[Pusher](-.8,.8),FRandom[Pusher](-.8,.8))).unit()*FRandom[Pusher](.1,1.2);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[Pusher](128,192));
|
|
}
|
|
numpt = Random[Pusher](2,6);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Pusher](-1,1),FRandom[Pusher](-1,1),FRandom[Pusher](-1,1)).unit()*FRandom[Pusher](2,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Pusher](3,6);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Pusher](-1,1),FRandom[Pusher](-1,1),FRandom[Pusher](-1,1)).unit()*FRandom[Pusher](2,8);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class BigPusherImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_QuakeEx(5,5,5,15,0,300,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:.8);
|
|
A_StartSound("pusher/althit",CHAN_VOICE);
|
|
A_SprayDecal("ImpactMark",-20);
|
|
int numpt = Random[Pusher](8,16);
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Pusher](-.8,.8),FRandom[Pusher](-.8,.8),FRandom[Pusher](-.8,.8))).unit()*FRandom[Pusher](.8,2.6);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[Pusher](128,192));
|
|
}
|
|
numpt = Random[Pusher](4,10);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Pusher](-1,1),FRandom[Pusher](-1,1),FRandom[Pusher](-1,1)).unit()*FRandom[Pusher](2,12);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Pusher](8,20);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Pusher](-1,1),FRandom[Pusher](-1,1),FRandom[Pusher](-1,1)).unit()*FRandom[Pusher](2,16);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class PusherProjectile : Actor
|
|
{
|
|
Vector3 oldvel;
|
|
double oldangle, oldpitch;
|
|
Actor lasthit;
|
|
int hittics;
|
|
|
|
Default
|
|
{
|
|
Obituary "$O_PUSHER";
|
|
Speed 50;
|
|
Radius 10;
|
|
Height 10;
|
|
DamageFunction clamp(int(3*vel.length()),0,150);
|
|
DamageType 'Tenderize';
|
|
BounceType "Hexen";
|
|
BounceFactor 1.0;
|
|
WallBounceFactor 1.0;
|
|
Gravity 0.3;
|
|
PROJECTILE;
|
|
+USEBOUNCESTATE;
|
|
+CANBOUNCEWATER;
|
|
+INTERPOLATEANGLES;
|
|
+DONTBOUNCEONSHOOTABLES;
|
|
-BOUNCEAUTOOFF;
|
|
+NODAMAGETHRUST;
|
|
-BOUNCEONUNRIPPABLES;
|
|
-ALLOWBOUNCEONACTORS;
|
|
+RIPPER;
|
|
-NOGRAVITY;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("pusher/fly",CHAN_BODY,CHANF_LOOP,1.,2.);
|
|
A_StartSound("pusher/altfire",CHAN_VOICE);
|
|
oldvel = vel;
|
|
oldangle = angle;
|
|
oldpitch = pitch;
|
|
}
|
|
void A_Reorient()
|
|
{
|
|
oldvel = vel;
|
|
oldangle = angle;
|
|
oldpitch = pitch;
|
|
if ( vel.length() <= 0. ) return;
|
|
Vector3 dir = vel.unit();
|
|
angle += clamp(deltaangle(angle,atan2(dir.y,dir.x)),-2,2);
|
|
pitch += clamp(deltaangle(pitch,asin(-dir.z)),-2,2);
|
|
// cancel if we hit a wall dead-on
|
|
FLineTraceData d;
|
|
LineTrace(oldangle,30,oldpitch,TRF_THRUACTORS|TRF_NOSKY,5,data:d);
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
angle = oldangle;
|
|
pitch = oldpitch;
|
|
ClearBounce();
|
|
special1 = 1;
|
|
ExplodeMissile(BlockingLine,BlockingMobj);
|
|
}
|
|
}
|
|
private bool HitSkyLine( Line l, int hitside )
|
|
{
|
|
if ( !l ) return false;
|
|
// if it's onesided, check if it's a Line_Horizon
|
|
if ( !l.sidedef[1] ) return (l.special == Line_Horizon);
|
|
Sector outs, ins;
|
|
if ( hitside )
|
|
{
|
|
ins = l.backsector;
|
|
outs = l.frontsector;
|
|
}
|
|
else
|
|
{
|
|
ins = l.frontsector;
|
|
outs = l.backsector;
|
|
}
|
|
// return true if we're in a sector with sky and we hit the upper part of the line
|
|
if ( ins.GetTexture(1) != skyflatnum ) return false;
|
|
return (outs.ceilingplane.ZAtPoint(pos.xy) <= pos.z);
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
Vector3 HitNormal = -vel.unit();
|
|
F3DFloor ff;
|
|
int lineside = 1;
|
|
if ( BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = BlockingFloor.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.top.Normal;
|
|
else HitNormal = BlockingFloor.floorplane.Normal;
|
|
}
|
|
else if ( BlockingCeiling )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = BlockingCeiling.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.bottom.Normal;
|
|
else HitNormal = BlockingCeiling.ceilingplane.Normal;
|
|
}
|
|
else if ( BlockingLine )
|
|
{
|
|
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
|
|
{
|
|
lineside = 0;
|
|
HitNormal *= -1;
|
|
}
|
|
}
|
|
else if ( BlockingMobj )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(pos,BlockingMobj.pos);
|
|
if ( (pos.x+radius) <= (BlockingMobj.pos.x-BlockingMobj.radius) )
|
|
HitNormal = (-1,0,0);
|
|
else if ( (pos.x-radius) >= (BlockingMobj.pos.x+BlockingMobj.radius) )
|
|
HitNormal = (1,0,0);
|
|
else if ( (pos.y+radius) <= (BlockingMobj.pos.y-BlockingMobj.radius) )
|
|
HitNormal = (0,-1,0);
|
|
else if ( (pos.y-radius) >= (BlockingMobj.pos.y+BlockingMobj.radius) )
|
|
HitNormal = (0,1,0);
|
|
else if ( pos.z >= (BlockingMobj.pos.z+BlockingMobj.height) )
|
|
HitNormal = (0,0,1);
|
|
else if ( (pos.z+height) <= BlockingMobj.pos.z )
|
|
HitNormal = (0,0,-1);
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
angle = oldangle;
|
|
pitch = oldpitch;
|
|
vel = oldvel;
|
|
// try to guess if we hit the sky
|
|
if ( HitSkyLine(BlockingLine,lineside) || (BlockingCeiling && (ceilingpic == skyflatnum)) || (BlockingFloor && (floorpic == skyflatnum)) )
|
|
{
|
|
special1 = 0;
|
|
ExplodeMissile();
|
|
return;
|
|
}
|
|
// re-do the bounce with our formula
|
|
vel = .8*((vel dot HitNormal)*HitNormal*(-1.8+FRandom[Pusher](.0,.8))+vel);
|
|
A_StartSound("pusher/bounce",volume:.3);
|
|
A_AlertMonsters(swwm_uncapalert?0:300);
|
|
if ( vel.length() < 5 )
|
|
{
|
|
special1 = 0;
|
|
ExplodeMissile();
|
|
}
|
|
}
|
|
void A_BecomePickup()
|
|
{
|
|
if ( special1 )
|
|
{
|
|
// stuff from direct hit
|
|
FLineTraceData d;
|
|
LineTrace(angle,40,pitch,0,5,data:d);
|
|
Vector3 HitNormal = -d.HitDir;
|
|
if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.top.Normal;
|
|
else HitNormal = d.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.bottom.Normal;
|
|
else HitNormal = d.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
HitNormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
|
|
if ( !d.LineSide ) HitNormal *= -1;
|
|
}
|
|
let p = Spawn("BigPusherImpact",d.HitLocation+HitNormal*4);
|
|
p.angle = atan2(HitNormal.y,HitNormal.x);
|
|
p.pitch = asin(-HitNormal.z);
|
|
bool busted = false;
|
|
if ( swwm_omnibust )
|
|
{
|
|
if ( BusterWall.BustLinetrace(d,100,target,d.HitDir,d.HitLocation.z) )
|
|
busted = true;
|
|
}
|
|
if ( busted ) pitch = 0.;
|
|
else bNOGRAVITY = true;
|
|
}
|
|
else pitch = 0;
|
|
gravity = 1.;
|
|
ClearBounce();
|
|
bSPECIAL = true;
|
|
A_SetSize(20,16);
|
|
A_ChangeLinkFlags(0);
|
|
A_StopSound(CHAN_BODY);
|
|
}
|
|
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
|
|
{
|
|
if ( target == lasthit ) return 0;
|
|
lasthit = target;
|
|
if ( target.bNOBLOOD || target.bDORMANT || target.bINVULNERABLE ) A_StartSound("pusher/althit",CHAN_WEAPON,CHANF_OVERLAP);
|
|
else A_StartSound("pusher/altmeat",CHAN_WEAPON,CHANF_OVERLAP);
|
|
target.A_QuakeEx(6,6,6,10,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.7);
|
|
SWWMUtility.DoKnockback(target,vel.unit(),85000);
|
|
return damage;
|
|
}
|
|
override void Touch( Actor toucher )
|
|
{
|
|
// cannot pick up swapweapon unless explicitly pressing use
|
|
let pw = GetDefaultByType("PusherWeapon");
|
|
SWWMWeapon sw;
|
|
if ( swwm_swapweapons && (sw = pw.HasSwapWeapon(toucher)) )
|
|
{
|
|
if ( toucher.CheckLocalView() )
|
|
Console.MidPrint(SmallFont,String.Format(StringTable.Localize("$SWWM_SWAPWEAPON"),sw.GetTag(),StringTable.Localize("$T_PUSHER")));
|
|
return;
|
|
}
|
|
let w = toucher.FindInventory("PusherWeapon");
|
|
if ( toucher.player && w )
|
|
{
|
|
let psp = toucher.player.GetPSPrite(PSP_WEAPON);
|
|
if ( psp && psp.CurState.InStateSequence(w.FindState("AltMiss")) )
|
|
return;
|
|
}
|
|
if ( !toucher.player || !toucher.GiveInventory("PusherWeapon",1) ) return;
|
|
if ( toucher.CheckLocalView() )
|
|
{
|
|
toucher.A_StartSound("misc/w_pkup",CHAN_ITEM,CHANF_NOPAUSE|CHANF_MAYBE_LOCAL);
|
|
let w = toucher.FindInventory("PusherWeapon");
|
|
if ( w ) w.PrintPickupMessage(true,w.PickupMessage());
|
|
}
|
|
else toucher.A_StartSound("misc/w_pkup",CHAN_ITEM,CHANF_MAYBE_LOCAL);
|
|
toucher.A_SelectWeapon("PusherWeapon");
|
|
Spawn("SWWMRedPickupFlash",pos);
|
|
Destroy();
|
|
}
|
|
override bool Used( Actor user )
|
|
{
|
|
// test vertical range
|
|
Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2));
|
|
double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2);
|
|
if ( abs(diff.z) > rang ) return false;
|
|
// if the toucher owns our SwapWeapon, drop it before picking us up
|
|
let pw = GetDefaultByType("PusherWeapon");
|
|
SWWMWeapon sw;
|
|
if ( swwm_swapweapons && (sw = pw.HasSwapWeapon(user)) )
|
|
{
|
|
bool swapto = false;
|
|
if ( sw == user.player.ReadyWeapon ) swapto = true;
|
|
user.DropInventory(sw);
|
|
// don't autoswitch just yet (hacky)
|
|
if ( swapto )
|
|
{
|
|
user.player.ReadyWeapon = null;
|
|
user.player.PendingWeapon = WP_NOCHANGE;
|
|
}
|
|
}
|
|
Touch(user);
|
|
return bDestroyed;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1 A_Reorient();
|
|
Wait;
|
|
Bounce:
|
|
XZW1 A 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
Death:
|
|
XZW1 A 0 A_BecomePickup();
|
|
XZW1 A 1 A_JumpIf(pos.z<=floorz,1);
|
|
Wait;
|
|
XZW1 A -1 A_StartSound("pusher/bounce");
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class PusherWeapon : SWWMWeapon
|
|
{
|
|
double chargelevel, vibe;
|
|
int hitcnt;
|
|
|
|
transient ui TextureID WeaponBox, ChargeBar;
|
|
transient ui DynamicValueInterpolator ChargeInter;
|
|
|
|
override void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
|
|
{
|
|
if ( !WeaponBox ) WeaponBox = TexMan.CheckForTexture("graphics/HUD/PusherDisplay.png",TexMan.Type_Any);
|
|
if ( !ChargeBar ) ChargeBar = TexMan.CheckForTexture("graphics/HUD/PusherBar.png",TexMan.Type_Any);
|
|
Screen.DrawTexture(WeaponBox,false,bx-60,by-9,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
|
|
int chg = clamp(ChargeInter?ChargeInter.GetValue():int(chargelevel*100),0,100);
|
|
double cw = chg*56./100.;
|
|
Screen.DrawTexture(ChargeBar,false,bx-58,by-7,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRightF,cw);
|
|
}
|
|
|
|
override void HudTick()
|
|
{
|
|
Super.HudTick();
|
|
if ( !ChargeInter ) ChargeInter = DynamicValueInterpolator.Create(int(chargelevel*100),.5,1,50);
|
|
ChargeInter.Update(int(chargelevel*100));
|
|
}
|
|
|
|
override bool ReportHUDAmmo()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
override Vector3 GetTraceOffset()
|
|
{
|
|
return (10.,2.,-3.5);
|
|
}
|
|
|
|
action void A_PusherDrill()
|
|
{
|
|
invoker.chargelevel = clamp(invoker.chargelevel+FRandom[Pusher](-.04,.08),.3,1.);
|
|
A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.5);
|
|
A_WeaponOffset(FRandom[Pusher](-1,1)*2,32+FRandom[Pusher](-1,1)*2);
|
|
A_ZoomFactor(1.01,ZOOM_INSTANT);
|
|
A_ZoomFactor(1.);
|
|
A_Recoil(-cos(pitch));
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-3.5*z);
|
|
FLineTraceData d;
|
|
LineTrace(angle,60,pitch,TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
|
|
SWWMBulletTrail.DoTrail(self,origin,x,60,0);
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
A_QuakeEx(2,2,2,7,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.8);
|
|
A_AlertMonsters(swwm_uncapalert?0:1200);
|
|
int dmg = int(3+invoker.chargelevel*7);
|
|
if ( invoker.chargelevel > .4 ) invoker.chargelevel -= FRandom[Pusher](.01,.04);
|
|
if ( d.HitType == TRACE_HitActor )
|
|
{
|
|
double diff = deltaangle(self.angle,AngleTo(d.HitActor));
|
|
self.angle += clamp(diff,-5.,5.);
|
|
SWWMUtility.DoKnockback(d.HitActor,d.HitDir,8500);
|
|
d.HitActor.A_QuakeEx(3,3,3,10,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.1);
|
|
dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Tenderize',DMG_THRUSTLESS);
|
|
if ( d.HitActor.bNOBLOOD || d.HitActor.bDORMANT || d.HitActor.bINVULNERABLE )
|
|
{
|
|
let p = Spawn("PusherImpact",d.HitLocation-d.HitDir*4);
|
|
p.angle = atan2(-d.HitDir.y,-d.HitDir.x);
|
|
p.pitch = asin(d.HitDir.z);
|
|
}
|
|
else
|
|
{
|
|
d.HitActor.TraceBleed(dmg,invoker);
|
|
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
|
|
d.HitActor.A_StartSound("pusher/meat",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
}
|
|
// move towards target (unless we're standing on it)
|
|
if ( !(self is 'Demolitionist') || (Demolitionist(self).oldencroached != d.HitActor) )
|
|
bJUSTATTACKED = true;
|
|
}
|
|
else
|
|
{
|
|
Vector3 HitNormal = -d.HitDir;
|
|
if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.top.Normal;
|
|
else HitNormal = d.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.bottom.Normal;
|
|
else HitNormal = d.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
HitNormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
|
|
if ( !d.LineSide ) HitNormal *= -1;
|
|
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation+HitNormal*4);
|
|
}
|
|
let p = Spawn("PusherImpact",d.HitLocation+HitNormal*4);
|
|
p.angle = atan2(HitNormal.y,HitNormal.x);
|
|
p.pitch = asin(-HitNormal.z);
|
|
if ( d.HitType == TRACE_HitFloor ) p.CheckSplash(40);
|
|
if ( waterlevel > 2 ) SWWMUtility.DoKnockback(self,-x,30000);
|
|
}
|
|
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,dmg,self,d.HitDir,d.HitLocation.z);
|
|
}
|
|
else if ( waterlevel > 2 ) SWWMUtility.DoKnockback(self,-x,18000);
|
|
if ( swwm_extraalert || !(invoker.hitcnt%20) ) A_AlertMonsters(swwm_uncapalert?0:500);
|
|
invoker.hitcnt++;
|
|
}
|
|
|
|
action void A_PusherAlt()
|
|
{
|
|
A_StopSound(CHAN_WEAPON);
|
|
if ( IsActorPlayingSound(CHAN_WEAPONEXTRA,"pusher/motor") )
|
|
A_StartSound("pusher/motorend",CHAN_WEAPONEXTRA,CHANF_DEFAULT,pitch:1.5);
|
|
A_StartSound("pusher/stop",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_StartSound("pusher/altfire",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_ZoomFactor(1.+invoker.chargelevel*.2,ZOOM_INSTANT);
|
|
A_ZoomFactor(1.);
|
|
A_Recoil(-(2.+4.*invoker.chargelevel)*cos(pitch));
|
|
A_QuakeEx(2+int(invoker.chargelevel*2),2+int(invoker.chargelevel*2),2+int(invoker.chargelevel*2),3+int(invoker.chargelevel*6),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.3+invoker.chargelevel*.7);
|
|
A_WeaponOffset(0,32);
|
|
A_Overlay(-9999,null);
|
|
A_PlayerFire();
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-3.5*z);
|
|
FLineTraceData d;
|
|
LineTrace(angle,80,pitch,TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
|
|
SWWMBulletTrail.DoTrail(self,origin,x,80,0);
|
|
bool gone = false;
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
A_QuakeEx(8,8,8,12,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.8);
|
|
A_AlertMonsters(swwm_uncapalert?0:1600);
|
|
int dmg = int(150*invoker.chargelevel);
|
|
if ( d.HitType == TRACE_HitActor )
|
|
{
|
|
double diff = deltaangle(self.angle,AngleTo(d.HitActor));
|
|
self.angle += clamp(diff,-5.,5.);
|
|
SWWMUtility.DoKnockback(d.HitActor,d.HitDir,85000);
|
|
d.HitActor.A_QuakeEx(9,9,9,15,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.1);
|
|
dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Tenderize',DMG_THRUSTLESS);
|
|
if ( d.HitActor.bNOBLOOD || d.HitActor.bDORMANT || d.HitActor.bINVULNERABLE )
|
|
{
|
|
let p = Spawn("BigPusherImpact",d.HitLocation-d.HitDir*4);
|
|
p.angle = atan2(-d.HitDir.y,-d.HitDir.x);
|
|
p.pitch = asin(d.HitDir.z);
|
|
}
|
|
else
|
|
{
|
|
d.HitActor.TraceBleed(dmg,invoker);
|
|
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
|
|
d.HitActor.A_StartSound("pusher/altmeat",CHAN_DAMAGE,CHANF_OVERLAP);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Vector3 HitNormal = -d.HitDir;
|
|
if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.top.Normal;
|
|
else HitNormal = d.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.bottom.Normal;
|
|
else HitNormal = d.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
HitNormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
|
|
if ( !d.LineSide ) HitNormal *= -1;
|
|
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation+HitNormal*4);
|
|
}
|
|
let p = Spawn("BigPusherImpact",d.HitLocation+HitNormal*4);
|
|
p.angle = atan2(HitNormal.y,HitNormal.x);
|
|
p.pitch = asin(-HitNormal.z);
|
|
if ( d.HitType == TRACE_HitFloor ) p.CheckSplash(40);
|
|
}
|
|
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,dmg,self,d.HitDir,d.HitLocation.z);
|
|
}
|
|
else if ( !Random[Pusher](0,3-int(invoker.chargelevel*3)) )
|
|
{
|
|
// didn't hit anything, randomly slip off
|
|
player.SetPSprite(PSP_WEAPON,ResolveState("AltMiss"));
|
|
A_StopSound(CHAN_WEAPON);
|
|
A_StopSound(CHAN_WEAPONEXTRA);
|
|
let p = Spawn("PusherProjectile",origin);
|
|
p.angle = angle;
|
|
p.pitch = pitch;
|
|
p.vel = x*p.speed*invoker.chargelevel;
|
|
p.target = self;
|
|
SWWMUtility.DoKnockback(self,x,85000.);
|
|
}
|
|
A_AlertMonsters(swwm_uncapalert?0:1200);
|
|
invoker.chargelevel = 0.;
|
|
}
|
|
|
|
action void A_BeginCharge()
|
|
{
|
|
invoker.chargelevel = invoker.vibe = 0.;
|
|
A_WeaponOffset(0,32);
|
|
A_QuakeEx(1,1,1,33,0,1,"",QF_RELATIVE|QF_SCALEUP,rollIntensity:.5);
|
|
A_Overlay(-9999,"Dummy");
|
|
}
|
|
|
|
action void A_ChargeUp()
|
|
{
|
|
invoker.chargelevel = min(1.,invoker.chargelevel+.025);
|
|
if ( invoker.chargelevel >= .5 ) invoker.vibe = min(1.,invoker.vibe+.02);
|
|
A_WeaponOffset(FRandom[Pusher](-1,1)*(invoker.chargelevel-invoker.vibe),32+FRandom[Pusher](-1,1)*(invoker.chargelevel-invoker.vibe));
|
|
if ( !(player.cmd.buttons&BT_ALTATTACK) && (invoker.chargelevel > .3) )
|
|
player.SetPSprite(PSP_WEAPON,ResolveState("AltRelease"));
|
|
}
|
|
|
|
Default
|
|
{
|
|
Tag "$T_PUSHER";
|
|
Inventory.PickupMessage "$I_PUSHER";
|
|
Obituary "$O_PUSHER";
|
|
Inventory.Icon "graphics/HUD/Icons/W_Pusher.png";
|
|
Weapon.UpSound "pusher/select";
|
|
Weapon.SlotNumber 1;
|
|
Weapon.SlotPriority 2.;
|
|
Weapon.SelectionOrder 1500;
|
|
Stamina 10000;
|
|
+WEAPON.MELEEWEAPON;
|
|
Radius 20;
|
|
Height 32;
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
Select:
|
|
XZW2 G 2 A_FullRaise();
|
|
XZW2 HIJKL 2;
|
|
Goto Ready;
|
|
Ready:
|
|
XZW2 A 1 A_WeaponReady(WRF_ALLOWRELOAD|WRF_ALLOWZOOM|WRF_ALLOWUSER1);
|
|
Wait;
|
|
Fire:
|
|
XZW2 A 2
|
|
{
|
|
A_WeaponOffset(0,32);
|
|
invoker.chargelevel = .2;
|
|
invoker.hitcnt = 0;
|
|
A_StartSound("pusher/start",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_StartSound("pusher/motor",CHAN_WEAPONEXTRA,CHANF_LOOP,pitch:1.5);
|
|
}
|
|
XZW2 M 2;
|
|
XZW2 N 0 A_StartSound("pusher/drill",CHAN_WEAPON,CHANF_LOOP);
|
|
Hold:
|
|
XZW2 N 1
|
|
{
|
|
A_WeaponOffset(FRandom[Pusher](-1,1),32+FRandom[Pusher](-1,1));
|
|
A_Recoil(cos(pitch));
|
|
}
|
|
XZW2 O 1 A_PusherDrill();
|
|
XZW2 P 1
|
|
{
|
|
A_WeaponOffset(FRandom[Pusher](-1,1),32+FRandom[Pusher](-1,1));
|
|
A_Recoil(cos(pitch));
|
|
}
|
|
XZW2 Q 1 A_PusherDrill();
|
|
XZW2 R 1
|
|
{
|
|
A_WeaponOffset(FRandom[Pusher](-1,1),32+FRandom[Pusher](-1,1));
|
|
A_Recoil(cos(pitch));
|
|
}
|
|
XZW2 S 1 A_PusherDrill();
|
|
XZW2 N 2
|
|
{
|
|
if ( player.cmd.buttons&BT_ATTACK )
|
|
return ResolveState("Hold");
|
|
invoker.chargelevel *= .4;
|
|
A_Recoil(.5*cos(pitch));
|
|
A_ClearRefire();
|
|
A_WeaponOffset(0,32);
|
|
A_StopSound(CHAN_WEAPON);
|
|
A_StartSound("pusher/stop",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_StartSound("pusher/motorend",CHAN_WEAPONEXTRA,CHANF_DEFAULT,pitch:1.5);
|
|
return ResolveState(null);
|
|
}
|
|
XZW2 T 2 { invoker.chargelevel = 0.; }
|
|
Goto Ready;
|
|
Dummy:
|
|
TNT1 A 1 A_ChargeUp();
|
|
Wait;
|
|
AltFire:
|
|
XZW2 A 2
|
|
{
|
|
A_StartSound("pusher/start",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_StartSound("pusher/motor",CHAN_WEAPONEXTRA,CHANF_LOOP,pitch:1.5);
|
|
A_StartSound("pusher/pullback",CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
XZW2 UVW 2;
|
|
XZW2 X 0 A_BeginCharge();
|
|
XZW2 XYZ 3;
|
|
XZW3 ABCDEFG 3;
|
|
XZW3 H 0
|
|
{
|
|
A_QuakeEx(1,1,1,8,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.5);
|
|
A_StartSound("pusher/motorend",CHAN_WEAPONEXTRA,CHANF_DEFAULT,pitch:1.5);
|
|
}
|
|
XZW3 H 3;
|
|
Wait;
|
|
AltRelease:
|
|
#### # 1 A_PusherAlt();
|
|
XZW5 R 2;
|
|
XZW3 IJKL 2;
|
|
XZW3 MNOP 3;
|
|
Goto Ready;
|
|
AltMiss:
|
|
XZW3 QRSTUVW 1;
|
|
TNT1 A -1
|
|
{
|
|
let nw = player.mo.PickNextWeapon();
|
|
// gross hack (don't prioritize Deep Impact if we have something better than it)
|
|
if ( nw is 'DeepImpact' )
|
|
{
|
|
player.ReadyWeapon = nw;
|
|
nw = player.mo.PickNextWeapon();
|
|
player.ReadyWeapon = invoker;
|
|
}
|
|
if ( nw != invoker ) player.PendingWeapon = nw;
|
|
RemoveInventory(invoker);
|
|
}
|
|
Stop;
|
|
Reload:
|
|
Zoom:
|
|
XZW2 A 2
|
|
{
|
|
A_StartSound("pusher/checkout",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_PlayerCheckGun();
|
|
}
|
|
XZW3 XYZ 2;
|
|
XZW4 ABCDEFGHIJKLMNOPQR 2;
|
|
XZW4 STUVWXYZ 3;
|
|
Goto Ready;
|
|
User1:
|
|
XZW2 A 2
|
|
{
|
|
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_PlayerMelee();
|
|
}
|
|
XZW5 ABC 2;
|
|
XZW5 D 1 A_Parry(9);
|
|
XZW5 EFGH 1;
|
|
XZW5 I 0 A_Melee(70,"demolitionist/whitl",1.1);
|
|
XZW5 IJ 2;
|
|
XZW5 K 2 { invoker.PlayUpSound(self); }
|
|
XZW5 LM 2;
|
|
XZW5 NOPQ 3;
|
|
Goto Ready;
|
|
Deselect:
|
|
XZW2 B 2 A_StartSound("pusher/deselect",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW2 CDEF 2;
|
|
XZW2 F -1 A_FullLower();
|
|
Stop;
|
|
}
|
|
}
|