Corrected various backface culling issues on weapons (rifle, peacemaker, autocannon). Fixed crash with dispersion pistol powerups due to typo. Fixed razorclaw and impaler not temporarily disabling invisibility on melee attack. Fixed rifle being visible with low zoom.
1042 lines
26 KiB
Text
1042 lines
26 KiB
Text
Class ImpalerAmmo : Ammo
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_IMPAMMO";
|
|
Inventory.Icon "I_Impale";
|
|
Inventory.PickupMessage "$I_IMPAMMO";
|
|
Inventory.Amount 3;
|
|
Inventory.MaxAmount 15;
|
|
Ammo.BackpackAmount 3;
|
|
Ammo.BackpackMaxAmount 30;
|
|
Ammo.DropAmount 3;
|
|
+INVENTORY.IGNORESKILL;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
IAMO A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
Class ImpalerAmmo2 : ImpalerAmmo
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_IMPAMMO2";
|
|
Inventory.PickupMessage "$I_IMPAMMO2";
|
|
Inventory.Amount 1;
|
|
Ammo.DropAmount 1;
|
|
+INVENTORY.IGNORESKILL;
|
|
}
|
|
}
|
|
|
|
Class ImpalerSpark : PulseSpark
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
ISPK A 1 Bright
|
|
{
|
|
A_FadeOut(FRandom[Pulse](0.,.15));
|
|
vel *= .96;
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class ViewImpalerSpark : ViewPulseSpark
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
ISPK A 1 Bright A_FadeOut(FRandom[Pulse](0.,.15));
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class ImpalerChunk : StingerChunk
|
|
{
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
let c = Spawn("UTSmoke",pos);
|
|
c.vel = vel*.3;
|
|
c.SetShade(Color(4,1,3)*Random[Impaler](48,63));
|
|
c.bBRIGHT = true;
|
|
c.alpha *= .5*alpha;
|
|
c.scale *= .5*scale.x;
|
|
}
|
|
}
|
|
|
|
Class ImpalerBurstLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "ImpExpl";
|
|
Args 0,0,0,50;
|
|
ReactionTime 15;
|
|
}
|
|
}
|
|
|
|
Class ImpalerBoltTracer : LineTracer
|
|
{
|
|
Actor ignoreme;
|
|
Array<HitListEntry> hitlist;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE )
|
|
{
|
|
let ent = new("HitListEntry");
|
|
ent.hitactor = Results.HitActor;
|
|
ent.hitlocation = Results.HitPos;
|
|
ent.x = Results.HitVector;
|
|
hitlist.Push(ent);
|
|
}
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
|
|
return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class ImpalerBurstBolt : Actor
|
|
{
|
|
Vector3 nextpos, nextdir;
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
frame++;
|
|
if ( frame >= 5 ) frame = 0;
|
|
}
|
|
action void A_Trace()
|
|
{
|
|
let t = new("ImpalerBoltTracer");
|
|
t.hitlist.Clear();
|
|
Vector3 x, y, z;
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
t.Trace(pos,CurSector,x,11.125,0);
|
|
for ( int i=0; i<t.hitlist.Size(); i++ )
|
|
{
|
|
UTMainHandler.DoKnockback(t.hitlist[i].hitactor,t.hitlist[i].x,6000);
|
|
t.hitlist[i].hitactor.DamageMobj(self,target,3,'Impaler',DMG_THRUSTLESS);
|
|
}
|
|
Vector3 normal = -t.Results.HitVector, dir = t.Results.HitVector;
|
|
if ( t.Results.HitType == TRACE_HitWall )
|
|
{
|
|
normal = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
|
|
if ( t.Results.Side ) normal *= -1;
|
|
t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos);
|
|
dir -= 2*normal*(dir dot normal);
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( t.Results.ffloor ) normal = -t.Results.ffloor.top.Normal;
|
|
else normal = t.Results.HitSector.floorplane.Normal;
|
|
dir -= 2*normal*(dir dot normal);
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( t.Results.ffloor ) normal = -t.Results.ffloor.bottom.Normal;
|
|
else normal = t.Results.HitSector.ceilingplane.Normal;
|
|
dir -= 2*normal*(dir dot normal);
|
|
}
|
|
else
|
|
{
|
|
// why the fuck
|
|
t.Results.HitPos = level.Vec3Offset(pos,x*10.125);
|
|
normal *= 0;
|
|
}
|
|
double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.8);
|
|
invoker.nextpos = level.Vec3Offset(t.Results.HitPos,normal);
|
|
invoker.nextdir = (dir+cos(a)*y*s+sin(a)*z*s).unit();
|
|
}
|
|
action void A_Spread()
|
|
{
|
|
Vector3 tdir = level.Vec3Diff(pos,invoker.nextpos);
|
|
double tdist = tdir.length();
|
|
tdir /= tdist;
|
|
for ( int i=2; i<tdist; i+=4 )
|
|
{
|
|
let s = Spawn("ImpalerSpark",level.Vec3Offset(pos,tdir*i));
|
|
s.vel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](.2,.8);
|
|
}
|
|
if ( special1 > 5 ) return;
|
|
let b = Spawn("ImpalerBurstBolt",invoker.nextpos);
|
|
b.angle = atan2(invoker.nextdir.y,invoker.nextdir.x);
|
|
b.pitch = asin(-invoker.nextdir.z);
|
|
b.target = target;
|
|
b.special1 = special1+1;
|
|
b.frame = frame;
|
|
}
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Obituary "$O_IMPALER1";
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+NOTELEPORT;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
PBLT A 1 Bright;
|
|
PBLT A 1 Bright A_Trace();
|
|
PBLT # 1 Bright A_Spread();
|
|
PBLT # 1 Bright A_FadeOut();
|
|
Wait;
|
|
Dummy:
|
|
PBLT ABCDE -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ImpalerBoltHit : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
Scale 0.3;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
#### ABCD 1 Bright;
|
|
Loop;
|
|
Dummy:
|
|
IHIT ABCD -1;
|
|
ICAP ABCD -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ImpalerBolt : Actor
|
|
{
|
|
const beamsize = 20.25;
|
|
|
|
ImpalerBoltTracer t;
|
|
ImpalerBolt next;
|
|
StarterImpalerBolt start;
|
|
Vector3 oldx;
|
|
Actor weffect;
|
|
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
if ( next ) next.Destroy();
|
|
if ( weffect ) weffect.Destroy();
|
|
A_StopSound(CHAN_BODY);
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
t = new("ImpalerBoltTracer");
|
|
if ( !(GetClass() is 'StarterImpalerBolt') )
|
|
A_PlaySound("impaler/beam",CHAN_BODY,.2,true,2.,pitch:.75);
|
|
}
|
|
void CheckBeam( Vector3 x )
|
|
{
|
|
t.HitList.Clear();
|
|
if ( bHITOWNER ) t.ignoreme = null;
|
|
else t.ignoreme = target;
|
|
t.Trace(pos,cursector,x,beamsize,0);
|
|
for ( int i=0; i<t.HitList.Size(); i++ )
|
|
{
|
|
if ( !(GetAge()%5) )
|
|
{
|
|
UTMainHandler.DoKnockback(t.hitlist[i].hitactor,t.hitlist[i].x,500*specialf1**2);
|
|
t.hitlist[i].hitactor.DamageMobj(self,target,int(2*specialf1**3),'Impaler',DMG_THRUSTLESS);
|
|
}
|
|
if ( start.Hitlist.Find(t.HitList[i].HitActor) == start.HitList.Size() )
|
|
start.Hitlist.Push(t.HitList[i].HitActor);
|
|
}
|
|
// seeking
|
|
double closest = 500;
|
|
if ( tracer )
|
|
{
|
|
double closest = Distance3D(tracer);
|
|
if ( (closest > 500) || (tracer.Health <= 0) )
|
|
{
|
|
tracer = null;
|
|
closest = 500;
|
|
}
|
|
}
|
|
let bt = BlockThingsIterator.Create(self,closest);
|
|
while ( bt.Next() )
|
|
{
|
|
let a = bt.Thing;
|
|
if ( !a || !a.bSHOOTABLE || !a.bISMONSTER || (a.Health <= 0) || !CheckSight(a) || (start.Hitlist.Find(a) < start.HitList.Size()) ) continue;
|
|
Vector3 dirto = level.Vec3Diff(pos,a.Vec3Offset(0,0,a.height/2));
|
|
double dist = dirto.length();
|
|
dirto /= dist;
|
|
if ( dirto dot x < .5 ) continue;
|
|
if ( dist > closest ) continue;
|
|
tracer = a;
|
|
closest = dist;
|
|
}
|
|
if ( tracer ) special1 = 0;
|
|
Vector3 normal = -t.Results.HitVector, dir = t.Results.HitVector;
|
|
if ( t.Results.HitType == TRACE_HitWall )
|
|
{
|
|
normal = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
|
|
if ( t.Results.Side ) normal *= -1;
|
|
t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos);
|
|
dir -= 2*normal*(dir dot normal);
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( t.Results.ffloor ) normal = -t.Results.ffloor.top.Normal;
|
|
else normal = t.Results.HitSector.floorplane.Normal;
|
|
dir -= 2*normal*(dir dot normal);
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( t.Results.ffloor ) normal = -t.Results.ffloor.bottom.Normal;
|
|
else normal = t.Results.HitSector.ceilingplane.Normal;
|
|
dir -= 2*normal*(dir dot normal);
|
|
}
|
|
else
|
|
{
|
|
// why the fuck
|
|
t.Results.HitPos = level.Vec3Offset(pos,x*beamsize);
|
|
normal *= 0;
|
|
}
|
|
if ( t.Results.HitType != TRACE_HitNone )
|
|
{
|
|
A_SprayDecal("BoltScorch",beamsize+8);
|
|
int numpt = Random[Impaler](10,20)*!Random[Impaler](0,2);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (normal+(FRandom[Impaler](-.6,.6),FRandom[Impaler](-.6,.6),FRandom[Impaler](-.6,.6))).unit()*FRandom[Impaler](2,4);
|
|
let s = Spawn("ImpalerSpark",t.Results.HitPos+normal*4);
|
|
s.vel = pvel;
|
|
}
|
|
}
|
|
Vector3 tdir = level.Vec3Diff(pos,t.Results.HitPos);
|
|
double tdist = tdir.length();
|
|
tdir /= tdist;
|
|
for ( int i=8; i<tdist; i+=16 )
|
|
{
|
|
let s = Spawn("ImpalerSpark",level.Vec3Offset(pos,tdir*i));
|
|
s.alpha *= .5;
|
|
s.scale *= .4;
|
|
s.vel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](.2,.8);
|
|
}
|
|
if ( (special1 < int(10*specialf1)) && (special2 < int(40*specialf1**.5)) && (start.hitlist.Size() < int(4*specialf1)) )
|
|
{
|
|
if ( !next )
|
|
{
|
|
next = ImpalerBolt(Spawn("ImpalerBolt",t.Results.HitPos+normal));
|
|
next.angle = atan2(dir.y,dir.x);
|
|
next.pitch = asin(-dir.z);
|
|
next.target = target;
|
|
next.start = start;
|
|
next.tracer = tracer;
|
|
next.special1 = special1+1;
|
|
next.special2 = special2+1;
|
|
next.specialf1 = max(1.,specialf1);
|
|
next.oldx = dir;
|
|
next.bHITOWNER = true;
|
|
}
|
|
else
|
|
{
|
|
next.tracer = tracer;
|
|
next.special1 = special1+1;
|
|
next.specialf1 = max(1.,specialf1);
|
|
next.bHITOWNER = true;
|
|
next.UpdateBeam(self,t.Results.HitPos+normal,dir);
|
|
}
|
|
if ( t.Results.HitType != TRACE_HitNone )
|
|
{
|
|
if ( !weffect ) weffect = Spawn("ImpalerBoltHit",t.Results.HitPos+normal*4);
|
|
weffect.SetOrigin(t.Results.HitPos+normal*4,true);
|
|
weffect.sprite = weffect.GetSpriteIndex('IHIT');
|
|
|
|
}
|
|
else if ( weffect ) weffect.Destroy();
|
|
return;
|
|
}
|
|
if ( !weffect ) weffect = Spawn("ImpalerBoltHit",t.Results.HitPos+normal*4);
|
|
if ( t.Results.HitType != TRACE_HitNone ) weffect.sprite = weffect.GetSpriteIndex('IHIT');
|
|
else weffect.sprite = weffect.GetSpriteIndex('ICAP');
|
|
weffect.SetOrigin(t.Results.HitPos+normal*4,true);
|
|
if ( next ) next.Destroy();
|
|
}
|
|
void UpdateBeam( ImpalerBolt parent, Vector3 ofs, Vector3 dir )
|
|
{
|
|
bRELATIVETOFLOOR = parent.bRELATIVETOFLOOR;
|
|
frame = parent.frame;
|
|
SetOrigin(ofs,true);
|
|
angle = atan2(dir.y,dir.x);
|
|
pitch = asin(-dir.z);
|
|
Vector3 x, y, z, dir;
|
|
double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.1);
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 dirto = (0,0,0);
|
|
double distto = 0.;
|
|
if ( tracer && (Distance3D(tracer) > 1) )
|
|
{
|
|
dirto = level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2));
|
|
distto = dirto.length();
|
|
dirto /= distto;
|
|
distto = 1.-clamp(distto*0.1,0.,.9);
|
|
}
|
|
dir = (x+y*cos(a)*s+z*sin(a)*s+dirto*distto).unit();
|
|
dir = dir*.5+oldx*.5;
|
|
oldx = dir;
|
|
A_SetAngle(atan2(dir.y,dir.x));
|
|
A_SetPitch(asin(-dir.z));
|
|
CheckBeam(dir);
|
|
}
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Obituary "$O_IMPALER2";
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+NOTELEPORT;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
PBLT # -1 Bright;
|
|
Stop;
|
|
Dummy:
|
|
PBLT ABCDE -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ImpalerFlare : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Scale 0.02;
|
|
Alpha 0.5;
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+NOTELEPORT;
|
|
+FORCEXYBILLBOARD;
|
|
+ROLLSPRITE;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
IFLA A -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class StarterImpalerBolt : ImpalerBolt
|
|
{
|
|
Array<Actor> hitlist;
|
|
Actor flares[2];
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
start = self;
|
|
flares[0] = Spawn("ImpalerFlare",pos);
|
|
flares[1] = Spawn("ImpalerFlare",pos);
|
|
oldx = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
if ( flares[0] ) flares[0].Destroy();
|
|
if ( flares[1] ) flares[1].Destroy();
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
frame++;
|
|
if ( frame > 4 ) frame = 0;
|
|
if ( isFrozen() ) return;
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Vector3 x, y, z, origin;
|
|
bRELATIVETOFLOOR = (target.pos.z <= target.floorz); // hack, but kinda works
|
|
if ( target.player )
|
|
{
|
|
[x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
|
|
origin = target.Vec2OffsetZ(0,0,target.player.viewz)+15*x+2*y-2.5*z;
|
|
}
|
|
else origin = target.Vec3Offset(0,0,target.missileheight);
|
|
SetOrigin(origin,true);
|
|
if ( !flares[0] ) flares[0] = Spawn("ImpalerFlare",pos);
|
|
if ( !flares[1] ) flares[1] = Spawn("ImpalerFlare",pos);
|
|
flares[1].bRELATIVETOFLOOR = flares[0].bRELATIVETOFLOOR = bRELATIVETOFLOOR;
|
|
flares[0].SetOrigin(pos,true);
|
|
flares[1].SetOrigin(pos,true);
|
|
flares[0].roll += 15.;
|
|
flares[1].roll += 5.;
|
|
flares[0].A_SetScale(0.01+cos(gametic*8)*0.002);
|
|
flares[1].A_SetScale(0.02+cos(gametic*8)*0.004);
|
|
double a = FRandom[Impaler](0,360), s = FRandom[Impaler](0.,.1);
|
|
[x, y, z] = dt_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
|
|
Vector3 dir = (x+cos(a)*y*s+sin(a)*z*s).unit();
|
|
dir = oldx*.5+dir*.5;
|
|
oldx = dir;
|
|
A_SetAngle(atan2(dir.y,dir.x));
|
|
A_SetPitch(asin(-dir.z));
|
|
hitlist.Clear();
|
|
CheckBeam((cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)));
|
|
}
|
|
}
|
|
|
|
Class ImpalerProjectile : Actor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_IMPALER1";
|
|
DamageType 'Impaler';
|
|
Speed 30;
|
|
Radius 4;
|
|
Height 4;
|
|
PROJECTILE;
|
|
+SKYEXPLODE;
|
|
+EXPLODEONWATER;
|
|
+FORCERADIUSDMG;
|
|
+NODAMAGETHRUST;
|
|
+INTERPOLATEANGLES;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_PlaySound("impaler/fly",CHAN_BODY,1.,true,2.);
|
|
}
|
|
action void A_ImpalerHit()
|
|
{
|
|
A_StopSound(CHAN_BODY);
|
|
bFORCEXYBILLBOARD = true;
|
|
scale *= 2.+special1*0.01;
|
|
A_AlertMonsters();
|
|
A_SetRenderStyle(1.,STYLE_Add);
|
|
A_NoGravity();
|
|
A_Explode(50+special1/2,120+special1/5);
|
|
UTMainHandler.DoBlast(self,120+special1/5,40000);
|
|
A_QuakeEx(2,2,2,5,0,250+special1/5,"",QF_RELATIVE|QF_SCALEDOWN,falloff:120+special1/2,rollintensity:0.2);
|
|
A_PlaySound("impaler/hit",CHAN_VOICE);
|
|
A_SprayDecal("ShockMark",20);
|
|
let l = Spawn("ImpalerBurstLight",pos);
|
|
l.Args[3] += special1/5;
|
|
double ang, pt;
|
|
int numpt = Random[Impaler](4,8);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[Impaler](0,360);
|
|
pt = FRandom[Impaler](-90,90);
|
|
let c = Spawn("ImpalerChunk",pos);
|
|
c.angle = ang;
|
|
c.pitch = pt;
|
|
c.vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](3,9);
|
|
}
|
|
numpt = Random[Impaler](6,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[Impaler](0,360);
|
|
pt = FRandom[Impaler](-90,90);
|
|
let c = Spawn("UTSmoke",pos);
|
|
c.vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](.3,.8);
|
|
c.SetShade(Color(4,1,3)*Random[Impaler](48,63));
|
|
c.bBRIGHT = true;
|
|
c.alpha *= .5;
|
|
}
|
|
numpt = Random[Impaler](8,15);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](1,3);
|
|
let s = Spawn("UTSmoke",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Impaler](8,16);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](2,6);
|
|
let s = Spawn("UTSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Impaler](20,30);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Impaler](-1,1),FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)).unit()*FRandom[Impaler](2,12);
|
|
let s = Spawn("UTChip",pos);
|
|
s.vel = pvel;
|
|
s.scale *= FRandom[Impaler](0.9,2.7);
|
|
}
|
|
numpt = 3+min(20,special1/10);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[Impaler](0,360);
|
|
pt = FRandom[Impaler](-90,90);
|
|
let b = Spawn("ImpalerBurstBolt",Vec3Offset(0,0,height/2));
|
|
b.angle = ang;
|
|
b.pitch = pt;
|
|
b.target = target;
|
|
b.special1 = Random[Impaler](-3,3);
|
|
b.special1 -= special1/30;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TPRJ A 1
|
|
{
|
|
roll += 15.;
|
|
double ang, pt;
|
|
int numpt;
|
|
if ( special1 > 80 )
|
|
{
|
|
numpt = special1/90;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[Impaler](0,360);
|
|
pt = FRandom[Impaler](-90,90);
|
|
let c = Spawn("ImpalerSpark",pos);
|
|
c.vel = vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](.2,.8)*(special1/90.);
|
|
}
|
|
}
|
|
if ( special1 > 120 )
|
|
{
|
|
numpt = special1/120;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[Impaler](0,360);
|
|
pt = FRandom[Impaler](-90,90);
|
|
let c = Spawn("UTSmoke",pos);
|
|
c.SetShade(Color(4,1,3)*Random[Impaler](48,63));
|
|
c.bBRIGHT = true;
|
|
c.alpha *= min(1.,special1/350.);
|
|
c.scale *= min(2.,special1/350.);
|
|
c.vel = vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[Impaler](.2,.8)*(special1/120.);
|
|
}
|
|
}
|
|
}
|
|
Wait;
|
|
Death:
|
|
TNT1 A 0 A_ImpalerHit();
|
|
TNT1 A 0 A_Jump(256,"Explo1","Explo2","Explo3");
|
|
Explo1:
|
|
IEX1 ABCDEFGHIJKLM 1 Bright;
|
|
Stop;
|
|
Explo2:
|
|
IEX2 ABCDEFGHIJKLM 1 Bright;
|
|
Stop;
|
|
Explo3:
|
|
IEX3 ABCDEFGHIJKLM 1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ImpalerLight : EnforcerLight
|
|
{
|
|
Default
|
|
{
|
|
args 255,128,224,80;
|
|
}
|
|
}
|
|
|
|
Class Impaler : UnrealWeapon
|
|
{
|
|
int ClipCount;
|
|
bool HasGem;
|
|
Actor beam;
|
|
|
|
property ClipCount : ClipCount;
|
|
|
|
override int, int, bool, bool GetClipAmount()
|
|
{
|
|
return ClipCount, -1, (ClipCount<10), false;
|
|
}
|
|
action void A_ImpalerFire()
|
|
{
|
|
A_Overlay(-9999,"Null");
|
|
A_Overlay(-3,"Null");
|
|
A_Overlay(-2,"Null");
|
|
A_PlaySound("impaler/fire",CHAN_WEAPON,Dampener.Active(self)?.1:1.);
|
|
invoker.FireEffect();
|
|
UTMainHandler.DoFlash(self,Color(16,224,64,255),1);
|
|
UTMainHandler.DoSwing(self,(FRandom[Impaler](-0.1,-0.2),FRandom[Impaler](-0.1,0.1)),4,-1.5,2,SWING_Spring,2,2);
|
|
if ( !Dampener.Active(self) ) A_AlertMonsters();
|
|
A_QuakeEx(1,1,1,4,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.1);
|
|
Vector3 x, y, z;
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-2.5*z);
|
|
Actor p = Spawn("ImpalerProjectile",origin);
|
|
p.angle = angle;
|
|
p.pitch = BulletSlope();
|
|
p.roll = FRandom[Impaler](0,360);
|
|
p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed;
|
|
p.target = self;
|
|
double mult = Amplifier.GetMult(self,100);
|
|
p.special1 = int(3*invoker.clipcount*mult+120*(mult-1.));
|
|
int numpt = Random[Impaler](8,16);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let s = Spawn("ViewImpalerSpark",origin);
|
|
ViewImpalerSpark(s).ofs = (10,2,-2.5);
|
|
s.target = self;
|
|
s.scale *= 3.;
|
|
ViewImpalerSpark(s).vvel += (FRandom[Impaler](0.2,0.8),FRandom[Impaler](-0.5,0.5),FRandom[Impaler](-0.5,0.5));
|
|
}
|
|
invoker.HasGem = false;
|
|
invoker.ClipCount = -1;
|
|
}
|
|
action void A_StartBeam()
|
|
{
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),15*x+2*y-2.5*z);
|
|
invoker.beam = Spawn("StarterImpalerBolt",origin);
|
|
invoker.beam.angle = angle;
|
|
invoker.beam.pitch = BulletSlope();
|
|
invoker.beam.target = self;
|
|
invoker.beam.specialf1 = Amplifier.GetMult(self,50);
|
|
}
|
|
action void A_DrainAmmo()
|
|
{
|
|
Weapon weap = Weapon(invoker);
|
|
if ( !weap ) return;
|
|
if ( !(invoker.special1%15) )
|
|
{
|
|
if ( invoker.ClipCount <= 0 ) return;
|
|
if ( invoker.beam )
|
|
invoker.beam.specialf1 = Amplifier.GetMult(self,10);
|
|
invoker.clipcount--;
|
|
}
|
|
invoker.FireEffect();
|
|
UTMainHandler.DoFlash(self,Color(16,255,32,255),3);
|
|
UTMainHandler.DoSwing(self,(FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)),0.05,0.05,8,SWING_Spring);
|
|
A_AlertMonsters();
|
|
Vector3 x, y, z;
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),15*x+2*y-2.5*z);
|
|
int numpt = Random[Impaler](4,7);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let s = Spawn("UTViewSmoke",origin);
|
|
UTViewSmoke(s).ofs = (15,2,-2.5);
|
|
s.scale *= 1.8;
|
|
s.target = self;
|
|
s.SetShade("602060");
|
|
s.A_SetRenderStyle(0.4,STYLE_AddShaded);
|
|
UTViewSmoke(s).vvel += (FRandom[Impaler](0.2,0.6),FRandom[Impaler](-0.2,0.2),FRandom[Impaler](-0.2,0.2));
|
|
}
|
|
numpt = Random[Impaler](8,16);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
let s = Spawn("ViewImpalerSpark",origin);
|
|
ViewImpalerSpark(s).ofs = (15,2,-2.5);
|
|
s.target = self;
|
|
s.scale *= 3.;
|
|
ViewImpalerSpark(s).vvel += (FRandom[Impaler](0.2,0.8),FRandom[Impaler](-0.5,0.5),FRandom[Impaler](-0.5,0.5));
|
|
}
|
|
}
|
|
action void A_StopBeam()
|
|
{
|
|
A_StopSound(CHAN_WEAPON);
|
|
if ( invoker.beam ) invoker.beam.Destroy();
|
|
}
|
|
override void OwnerDied()
|
|
{
|
|
Super.OwnerDied();
|
|
if ( beam ) beam.Destroy();
|
|
}
|
|
override void DetachFromOwner()
|
|
{
|
|
if ( beam ) beam.Destroy();
|
|
Super.DetachFromOwner();
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
if ( beam ) beam.Destroy();
|
|
}
|
|
private action bool TryHit( double angle, int dmg )
|
|
{
|
|
FTranslatedLineTarget t;
|
|
double slope = AimLineAttack(angle,DEFMELEERANGE*1.5,t,0.,ALF_CHECK3D);
|
|
FLineTraceData d;
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),y*4-z*4);
|
|
LineTrace(angle,DEFMELEERANGE*1.5,slope,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
if ( d.HitType == TRACE_HitActor )
|
|
{
|
|
if ( d.HitLocation.z >= (d.HitActor.pos.z+d.HitActor.height*0.8) )
|
|
dmg = d.HitActor.DamageMobj(invoker,self,dmg*2,'Decapitated',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
|
|
else dmg = d.HitActor.DamageMobj(invoker,self,dmg,'slashed',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
|
|
UTMainHandler.DoKnockback(d.HitActor,d.HitDir,12000);
|
|
if ( d.HitActor.player ) d.HitActor.A_QuakeEx(2,2,2,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.25);
|
|
if ( !d.HitActor.bNOBLOOD )
|
|
{
|
|
d.HitActor.TraceBleed(dmg,invoker);
|
|
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
|
|
}
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation-d.HitDir*4);
|
|
A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12);
|
|
if ( !d.HitActor || d.HitActor.bNOBLOOD )
|
|
{
|
|
A_PlaySound("impaler/wall",CHAN_WEAPON);
|
|
let p = Spawn("SawImpact",d.HitLocation-d.HitDir*4);
|
|
p.angle = atan2(d.HitDir.y,d.HitDir.x);
|
|
p.pitch = asin(-d.HitDir.z);
|
|
}
|
|
else A_PlaySound("impaler/flesh",CHAN_WEAPON);
|
|
A_AlertMonsters();
|
|
A_QuakeEx(1,1,1,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.06);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
action void A_Stab()
|
|
{
|
|
invoker.FireEffect();
|
|
UTMainHandler.DoSwing(self,(FRandom[Impaler](-1,1),FRandom[Impaler](-1,1)),0.3,-0.2,2,SWING_Spring,0,2);
|
|
for ( int i=0; i<8; i++ ) if ( TryHit(angle+i*(45./16),15) || TryHit(angle-i*(45./16),15) ) return;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( Owner.player.ReadyWeapon != self ) return;
|
|
if ( (Owner.waterlevel > 2) && !(level.maptime%5) )
|
|
ClipCount = max(0,ClipCount-1);
|
|
let psp = Owner.player.FindPSprite(-2);
|
|
if ( psp ) psp.alpha = clamp(ClipCount/double(default.ClipCount),0.,1.);
|
|
}
|
|
override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount )
|
|
{
|
|
if ( ClipCount > 0 ) return true;
|
|
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
|
|
}
|
|
Default
|
|
{
|
|
Tag "$T_IMPALER";
|
|
Inventory.PickupMessage "$I_IMPALER";
|
|
Weapon.UpSound "impaler/select";
|
|
Weapon.SlotNumber 7;
|
|
Weapon.SelectionOrder 0;
|
|
Weapon.SlotPriority 0.9;
|
|
Weapon.AmmoType "ImpalerAmmo";
|
|
Weapon.AmmoUse 1;
|
|
Weapon.AmmoType2 "ImpalerAmmo";
|
|
Weapon.AmmoUse2 1;
|
|
Weapon.AmmoGive 6;
|
|
UTWeapon.DropAmmo 3;
|
|
Impaler.ClipCount 30;
|
|
+WEAPON.AMMO_OPTIONAL;
|
|
+WEAPON.ALT_AMMO_OPTIONAL;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
IMPP A -1;
|
|
Stop;
|
|
IMPP B -1;
|
|
Stop;
|
|
Select:
|
|
IMPS A 1 A_Raise(int.max);
|
|
Wait;
|
|
Ready:
|
|
IMPS ABCDEF 3 A_WeaponReady(WRF_NOFIRE);
|
|
IMPI A 0
|
|
{
|
|
let weap = Weapon(invoker);
|
|
invoker.HasGem = false;
|
|
if ( (invoker.ClipCount>=0) || (weap.Ammo1.Amount > 0) )
|
|
return ResolveState("Reload");
|
|
return ResolveState("Idle");
|
|
}
|
|
Goto Idle;
|
|
Dummy:
|
|
TNT1 A 1
|
|
{
|
|
let weap = Weapon(invoker);
|
|
int flags = 0;
|
|
if ( weap.Ammo1.Amount > 0 ) flags |= WRF_ALLOWRELOAD;
|
|
if ( invoker.HasGem && (invoker.ClipCount <= 0) ) flags |= WRF_NOSECONDARY;
|
|
A_WeaponReady(flags);
|
|
}
|
|
Wait;
|
|
Idle:
|
|
IMPI A 0 A_Overlay(-9999,"Dummy");
|
|
IMPI ABCDEFGH 10;
|
|
Goto Idle+1;
|
|
Melee:
|
|
IMPM A 2
|
|
{
|
|
A_Overlay(-9999,"Null");
|
|
A_PlaySound("impaler/stab",CHAN_WEAPON);
|
|
}
|
|
IMPM BC 2;
|
|
IMPM D 2 A_Stab();
|
|
IMPM EFGHIJ 2;
|
|
Goto Idle;
|
|
Fire:
|
|
IMPF A 0
|
|
{
|
|
if ( !invoker.HasGem )
|
|
return ResolveState("Melee");
|
|
A_ImpalerFire();
|
|
return ResolveState(null);
|
|
}
|
|
IMPF ABCDEFGHI 2;
|
|
IMPI A 0 A_JumpIfNoAmmo("Idle");
|
|
Goto Reload;
|
|
AltFire:
|
|
IMPA A 0
|
|
{
|
|
if ( !invoker.HasGem )
|
|
return ResolveState("Melee");
|
|
A_Overlay(-9999,"Null");
|
|
player.SetPSprite(-3,invoker.FindState("GemAltFire"));
|
|
player.SetPSprite(-2,invoker.FindState("ZapAltFire"));
|
|
return ResolveState(null);
|
|
}
|
|
IMPA ABCDEFGH 2;
|
|
IMPA I 0
|
|
{
|
|
A_PlaySound("impaler/altfire",CHAN_WEAPON,looping:true);
|
|
A_StartBeam();
|
|
invoker.special1 = 0;
|
|
}
|
|
Goto AltHold;
|
|
AltHold:
|
|
IMPA IJKLMNOP 2
|
|
{
|
|
A_DrainAmmo();
|
|
invoker.special1++;
|
|
return A_JumpIf((invoker.ClipCount<=0)||!(player.cmd.buttons&BT_ALTATTACK),"AltRelease");
|
|
}
|
|
Loop;
|
|
AltRelease:
|
|
IMPA Q 0
|
|
{
|
|
player.SetPSprite(-3,invoker.FindState("GemAltRelease"));
|
|
player.SetPSprite(-2,invoker.FindState("ZapAltRelease"));
|
|
A_StopBeam();
|
|
A_PlaySound("impaler/gem",CHAN_WEAPON,looping:true);
|
|
}
|
|
IMPA QRSTUVWX 2;
|
|
Goto Idle;
|
|
Reload:
|
|
IMPG A 0
|
|
{
|
|
A_Overlay(-9999,"Null");
|
|
invoker.HasGem = !invoker.HasGem;
|
|
if ( invoker.HasGem )
|
|
{
|
|
let weap = Weapon(invoker);
|
|
if ( (invoker.ClipCount < 0) && (weap.Ammo1.Amount > 0) )
|
|
{
|
|
weap.DepleteAmmo(false,true,1);
|
|
invoker.ClipCount = invoker.default.ClipCount;
|
|
}
|
|
A_Overlay(-3,"GemUp");
|
|
A_Overlay(-2,"ZapUp");
|
|
A_OverlayFlags(-2,PSPF_RenderStyle|PSPF_ForceStyle|PSPF_Alpha|PSPF_ForceAlpha,true);
|
|
A_OverlayRenderStyle(-2,STYLE_Add);
|
|
A_PlaySound("impaler/gem",CHAN_WEAPON,looping:true);
|
|
}
|
|
else
|
|
{
|
|
player.SetPSprite(-3,invoker.FindState("GemDown"));
|
|
player.SetPSprite(-2,invoker.FindState("ZapDown"));
|
|
A_PlaySound("impaler/gemdown",CHAN_WEAPON);
|
|
}
|
|
if ( self is 'UTPlayer' ) UTPlayer(self).PlayReloading();
|
|
}
|
|
IMPG ABCDE 2;
|
|
Goto Idle;
|
|
Deselect:
|
|
IMPD A 0 A_Overlay(-9999,"Null");
|
|
IMPD A 0 A_JumpIf(!invoker.HasGem,"FullDeselect");
|
|
IMPG A 0
|
|
{
|
|
invoker.HasGem = false;
|
|
player.SetPSprite(-3,invoker.FindState("GemDown"));
|
|
player.SetPSprite(-2,invoker.FindState("ZapDown"));
|
|
A_PlaySound("impaler/gemdown",CHAN_WEAPON);
|
|
}
|
|
IMPG ABCDE 2;
|
|
Goto FullDeselect;
|
|
FullDeselect:
|
|
IMPD ABCDEF 2;
|
|
IMPD F 1 A_Lower(int.max);
|
|
Wait;
|
|
GemUp:
|
|
IMGS ABCDE 2;
|
|
Goto GemIdle;
|
|
GemIdle:
|
|
IMGI ABCDEFGH 10;
|
|
Loop;
|
|
GemDown:
|
|
IMGD ABCDE 2;
|
|
Stop;
|
|
GemAltFire:
|
|
IMGA ABCDEFGH 2;
|
|
Goto GemAltHold;
|
|
GemAltHold:
|
|
IMGA IJKLMNOP 2;
|
|
Loop;
|
|
GemAltRelease:
|
|
IMGA QRSTUVWX 2;
|
|
Goto GemIdle;
|
|
ZapUp:
|
|
IMZS ABCDE 2 Bright;
|
|
Goto ZapIdle;
|
|
ZapIdle:
|
|
IMZI ABCDEFGH 10 Bright;
|
|
Loop;
|
|
ZapDown:
|
|
IMZD ABCDE 2 Bright;
|
|
Stop;
|
|
ZapAltFire:
|
|
IMZA ABCDEFGH 2 Bright;
|
|
Goto ZapAltHold;
|
|
ZapAltHold:
|
|
IMZA IJKLMNOP 2 Bright;
|
|
Loop;
|
|
ZapAltRelease:
|
|
IMZA QRSTUVWX 2 Bright;
|
|
Goto ZapIdle;
|
|
}
|
|
}
|