376 lines
10 KiB
Text
376 lines
10 KiB
Text
// Pusher projectiles and effects
|
|
|
|
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](1,3);
|
|
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](0,2);
|
|
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](1,3);
|
|
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;
|
|
bool bUsePickup;
|
|
|
|
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 )
|
|
{
|
|
if ( toucher.player && swwm_usetopickup && !bUsePickup )
|
|
return;
|
|
// 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;
|
|
}
|
|
}
|
|
bUsePickup = true;
|
|
Touch(user);
|
|
bUsePickup = false;
|
|
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;
|
|
}
|
|
}
|