2935 lines
78 KiB
Text
2935 lines
78 KiB
Text
// Blackmann "Rhino Stopper" Spreadgun (from Instant Action 3, also planned for Zanaveth Ultra Suite 2)
|
|
// Slot 3, replaces Shotgun, Ethereal Crossbow, Serpent Staff
|
|
|
|
Class RedShellCasing : SWWMCasing
|
|
{
|
|
Default
|
|
{
|
|
BounceSound "spreadgun/casing";
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
heat = 0;
|
|
}
|
|
}
|
|
Class GreenShellCasing : RedShellCasing {}
|
|
Class WhiteShellCasing : RedShellCasing {}
|
|
Class BlueShellCasing : RedShellCasing {}
|
|
Class BlackShellCasing : RedShellCasing {}
|
|
Class PurpleShellCasing : RedShellCasing {}
|
|
Class GoldShellCasing : RedShellCasing
|
|
{
|
|
Default
|
|
{
|
|
BounceSound "spreadgun/gcasing";
|
|
}
|
|
}
|
|
|
|
Class SWWMDamageAccumulator : Thinker
|
|
{
|
|
Actor victim, inflictor, source;
|
|
Array<Int> amounts;
|
|
int total;
|
|
Name type;
|
|
bool dontgib;
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
// so many damn safeguards in this
|
|
if ( !victim )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
int gibhealth = victim.GetGibHealth();
|
|
// おまえはもう死んでいる
|
|
if ( (victim.health-total <= gibhealth) && !dontgib )
|
|
{
|
|
// safeguard for inflictors that have somehow ceased to exist, which apparently STILL CAN HAPPEN
|
|
if ( inflictor ) inflictor.bEXTREMEDEATH = true;
|
|
else type = 'Extreme';
|
|
}
|
|
// make sure accumulation isn't reentrant
|
|
if ( inflictor && (inflictor is 'EvisceratorChunk') ) inflictor.bAMBUSH = true;
|
|
// 何?
|
|
for ( int i=0; i<amounts.Size(); i++ )
|
|
{
|
|
if ( !victim ) break;
|
|
victim.DamageMobj(inflictor,source,amounts[i],type,DMG_THRUSTLESS);
|
|
}
|
|
// clean up
|
|
if ( inflictor )
|
|
{
|
|
if ( inflictor is 'EvisceratorChunk' ) inflictor.bAMBUSH = false;
|
|
inflictor.bEXTREMEDEATH = false;
|
|
}
|
|
Destroy();
|
|
}
|
|
|
|
static void Accumulate( Actor victim, int amount, Actor inflictor, Actor source, Name type, bool dontgib = false )
|
|
{
|
|
if ( !victim ) return;
|
|
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
|
|
SWWMDamageAccumulator a, match = null;
|
|
while ( a = SWWMDamageAccumulator(ti.Next()) )
|
|
{
|
|
if ( a.victim != victim ) continue;
|
|
match = a;
|
|
break;
|
|
}
|
|
if ( !match )
|
|
{
|
|
match = new("SWWMDamageAccumulator");
|
|
match.ChangeStatNum(STAT_USER);
|
|
match.victim = victim;
|
|
match.amounts.Clear();
|
|
}
|
|
match.amounts.Push(amount);
|
|
match.total += amount;
|
|
match.inflictor = inflictor;
|
|
match.source = source;
|
|
match.type = type;
|
|
match.dontgib = dontgib;
|
|
}
|
|
|
|
static clearscope int GetAmount( Actor victim )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
|
|
SWWMDamageAccumulator a, match = null;
|
|
while ( a = SWWMDamageAccumulator(ti.Next()) )
|
|
{
|
|
if ( a.victim != victim ) continue;
|
|
return a.total;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
Class SpreadgunTracer : LineTracer
|
|
{
|
|
Actor ignoreme;
|
|
Array<HitListEntry> hitlist;
|
|
Array<Line> shootthroughlist;
|
|
Array<WaterHit> waterhitlist;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.CrossedWater;
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.Crossed3DWater;
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE )
|
|
{
|
|
int amt = SWWMDamageAccumulator.GetAmount(Results.HitActor);
|
|
// getgibhealth isn't clearscope, fuck
|
|
int gibhealth = -int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor);
|
|
if ( Results.HitActor.GibHealth != int.min ) gibhealth = -abs(Results.HitActor.GibHealth);
|
|
// if gibbed, go through without dealing more damage
|
|
if ( Results.HitActor.health-amt <= gibhealth ) return TRACE_Skip;
|
|
let ent = new("HitListEntry");
|
|
ent.hitactor = Results.HitActor;
|
|
ent.hitlocation = Results.HitPos;
|
|
ent.x = Results.HitVector;
|
|
hitlist.Push(ent);
|
|
// go right on through if dead
|
|
if ( Results.HitActor.health-amt <= 0 ) return TRACE_Skip;
|
|
// stap
|
|
return TRACE_Stop;
|
|
}
|
|
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;
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SpreadSlugTracer : SpreadgunTracer
|
|
{
|
|
double penetration; // please don't laugh
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.CrossedWater;
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.Crossed3DWater;
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
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;
|
|
ent.hitdamage = min(Results.HitActor.health+int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor),int(penetration));
|
|
hitlist.Push(ent);
|
|
penetration = max(0,penetration-ent.hitdamage);
|
|
if ( penetration <= 0 ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
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;
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SpreadImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( swwm_extraalert ) A_AlertMonsters(200);
|
|
A_StartSound("spreadgun/pellet",CHAN_VOICE,CHANF_DEFAULT,.4,4.);
|
|
A_SprayDecal("TinyBulletChip",-20);
|
|
int numpt = Random[Spreadgun](2,4)-special1;
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8))).unit()*FRandom[Spreadgun](.1,1.2);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.scale *= .6;
|
|
s.special1 = Random[Spreadgun](0,1);
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
}
|
|
numpt = Random[Spreadgun](-1,3)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Spreadgun](2,4)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](1,4);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class SlugImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( swwm_extraalert ) A_AlertMonsters(500);
|
|
A_StartSound("spreadgun/slug",CHAN_VOICE,CHANF_DEFAULT,1.,2.);
|
|
A_SprayDecal("BulletChip",-20);
|
|
int numpt = Random[Spreadgun](5,10)-special1;
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Spreadgun](-.4,.4),FRandom[Spreadgun](-.4,.4),FRandom[Spreadgun](-.4,.4))).unit()*FRandom[Spreadgun](.4,2.);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.scale *= 1.4;
|
|
s.special1 = Random[Spreadgun](0,2);
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
}
|
|
numpt = Random[Spreadgun](2,5)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Spreadgun](4,8)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class DragonBreathPuff : Actor
|
|
{
|
|
Vector2 initsc;
|
|
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Scale 0.5;
|
|
Alpha 0.35;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
+NOTELEPORT;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
scale *= FRandom[Spreadgun](.8,1.);
|
|
alpha *= FRandom[Spreadgun](.8,1.);
|
|
roll = FRandom[Spreadgun](0,360);
|
|
SetState(FindState("Spawn")+Random[Spreadgun](0,19));
|
|
initsc = scale;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_FadeOut((waterlevel>0)?.1:.02);
|
|
scale += initsc*.2;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XFLM ABCDEFGHIJKLMNOPQRST -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class DragonBreathArm : Actor
|
|
{
|
|
Vector3 oldvel;
|
|
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_WHITE";
|
|
DamageType 'Fire';
|
|
PROJECTILE;
|
|
+THRUACTORS;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+USEBOUNCESTATE;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
-NOGRAVITY;
|
|
Gravity 0.15;
|
|
BounceFactor 1.0;
|
|
Radius 4;
|
|
Height 4;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
reactiontime = Random[ExploS](18,24);
|
|
vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*FRandom[ExploS](16.,24.);
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
Vector3 HitNormal = -vel.unit();
|
|
F3DFloor ff;
|
|
if ( BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = CurSector.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<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = CurSector.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) )
|
|
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
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
vel = .8*((vel dot HitNormal)*HitNormal*(-1.8+FRandom[Spreadgun](.0,.8))+vel);
|
|
bHITOWNER = true;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1
|
|
{
|
|
if ( waterlevel > 0 ) ReactionTime -= 2;
|
|
A_CountDown();
|
|
let p = Spawn("DragonBreathPuff",pos);
|
|
p.alpha *= .6+.4*(ReactionTime/20.);
|
|
p.scale *= 3.5-2.5*(ReactionTime/20.);
|
|
if ( !(ReactionTime%3) )
|
|
{
|
|
let l = Spawn("PaletteLight",pos);
|
|
l.Args[3] = int(90+50*(ReactionTime/20.));
|
|
l.ReactionTime = int(2+8*(ReactionTime/20.));
|
|
l.target = p;
|
|
}
|
|
if ( !(ReactionTime%2) )
|
|
{
|
|
SWWMHandler.DoBlast(self,150-6*reactiontime,1000+200*reactiontime,bHITOWNER?null:target);
|
|
A_Explode(1+int(reactiontime/1.5),150-6*reactiontime,bHITOWNER?XF_HURTSOURCE:0);
|
|
}
|
|
double spd = vel.length();
|
|
vel = (vel*.4+(FRandom[ExploS](-.2,.2),FRandom[ExploS](-.2,.2),FRandom[ExploS](-.2,.2))).unit()*spd;
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,5);
|
|
if ( !(ReactionTime%2) )
|
|
{
|
|
let s = Spawn("SWWMHalfSmoke",pos);
|
|
s.vel = pvel+vel*.2;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](96,192));
|
|
s.special1 = Random[ExploS](2,4);
|
|
s.scale *= 2.4;
|
|
s.alpha *= .1+.2*(ReactionTime/20.);
|
|
int numpt = Random[Spreadgun](-2,4);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = vel+(FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,4);
|
|
let s2 = Spawn("SWWMSpark",pos);
|
|
s2.scale *= .4;
|
|
s2.vel = pvel;
|
|
}
|
|
}
|
|
}
|
|
Wait;
|
|
Bounce:
|
|
TNT1 A 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
}
|
|
}
|
|
|
|
Class SaltTracer : LineTracer
|
|
{
|
|
Actor ignore;
|
|
Array<Line> ShootThroughList;
|
|
Array<WaterHit> WaterHitList;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.CrossedWater;
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.Crossed3DWater;
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignore ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BlockHitscan) )
|
|
return TRACE_Stop;
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SaltLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SaltTail";
|
|
ReactionTime 30;
|
|
Args 0,0,0,240;
|
|
}
|
|
}
|
|
Class SaltLight2 : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SaltExpl";
|
|
ReactionTime 30;
|
|
Args 0,0,0,70;
|
|
}
|
|
}
|
|
|
|
Class SaltImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_BLUE";
|
|
DamageType "Electricity";
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
Scale 1.8;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
+FOILINVUL;
|
|
+NOINTERACTION;
|
|
}
|
|
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( args[0] == 1 ) return StringTable.Localize("$O_WALLBUSTER_BLUE");
|
|
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_AlertMonsters(6000);
|
|
SWWMHandler.DoBlast(self,120,15000);
|
|
A_Explode(25+special2*5,120);
|
|
A_QuakeEx(3,3,3,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN,falloff:150,rollintensity:0.2);
|
|
A_StartSound("spreadgun/salt",CHAN_VOICE,attenuation:.35);
|
|
A_SprayDecal("ShockMarkSmall",-172);
|
|
A_SprayDecal("SaltMark",-172);
|
|
Scale *= FRandom[ExploS](0.8,1.1);
|
|
int numpt = Random[ExploS](5,9)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,6);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,3,4)*Random[ExploS](48,63));
|
|
s.special1 = Random[ExploS](1,2);
|
|
s.scale *= 2.4;
|
|
s.A_SetRenderStyle(.4,STYLE_AddShaded);
|
|
}
|
|
numpt = Random[ExploS](3,9)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,6);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[ExploS](3,6)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Spawn("SaltLight2",pos);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 0 NoDelay A_Jump(256,"Expl1","Expl2","Expl3");
|
|
Expl1:
|
|
KSX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
Expl2:
|
|
KSX2 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
Expl3:
|
|
KSX3 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SaltBeam : Actor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_BLUE";
|
|
DamageType "Electricity";
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
Stamina 9;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FOILINVUL;
|
|
+NOINTERACTION;
|
|
}
|
|
|
|
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( args[1] == 1 ) return StringTable.Localize("$O_WALLBUSTER_BLUE");
|
|
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
|
}
|
|
void SpreadOut()
|
|
{
|
|
special1 = 1;
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
let t = new("SaltTracer");
|
|
t.ignore = target;
|
|
t.Trace(pos,cursector,x,32,TRACE_HitSky);
|
|
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
|
|
{
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
|
|
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
|
|
}
|
|
for ( int i=0; i<t.WaterHitList.Size(); i++ )
|
|
{
|
|
let b = Actor.Spawn("InvisibleSplasher",t.WaterHitList[i].hitpos);
|
|
b.A_CheckTerrain();
|
|
}
|
|
for ( int i=4; i<t.Results.Distance; i+=8 )
|
|
{
|
|
if ( Random[Spreadgun](0,Stamina) ) continue;
|
|
let b = Actor.Spawn("SWWMHalfSmoke",level.Vec3Offset(pos,x*i));
|
|
b.Scale *= FRandom[Spreadgun](.6,.8);
|
|
b.special1 = Random[Spreadgun](1,2);
|
|
b.A_SetRenderStyle(.3,STYLE_AddShaded);
|
|
b.SetShade(Color(1,3,4)*Random[Spreadgun](48,63));
|
|
b.vel += x*FRandom[Spreadgun](-.5,3);
|
|
}
|
|
if ( t.Results.HitType != TRACE_HitNone )
|
|
{
|
|
if ( args[1] ) BusterWall.Bust(t.Results,85+Accuracy*10,target,x,t.Results.HitPos.z);
|
|
if ( t.Results.HitType == TRACE_HitActor )
|
|
{
|
|
SWWMHandler.DoKnockback(t.Results.HitActor,x,25000);
|
|
t.Results.HitActor.DamageMobj(self,target,60+Accuracy*5,'Salt',DMG_THRUSTLESS);
|
|
}
|
|
Vector3 norm = -x;
|
|
if ( t.Results.HitType == TRACE_HitWall )
|
|
{
|
|
norm = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
|
|
if ( t.Results.Side ) norm *= -1;
|
|
t.Results.HitLine.RemoteActivate(tracer,t.Results.Side,SPAC_Impact,t.Results.HitPos);
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( t.Results.ffloor ) norm = -t.Results.ffloor.top.Normal;
|
|
else norm = t.Results.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( t.Results.ffloor ) norm = -t.Results.ffloor.bottom.Normal;
|
|
else norm = t.Results.HitSector.ceilingplane.Normal;
|
|
}
|
|
if ( t.Results.HitType != TRACE_HasHitSky )
|
|
{
|
|
let i = Spawn("SaltImpact",level.Vec3Offset(t.Results.HitPos,norm*4));
|
|
i.angle = atan2(norm.y,norm.x);
|
|
i.pitch = asin(-norm.z);
|
|
i.target = target;
|
|
i.special1 = (Stamina-9)/4;
|
|
i.special2 = Accuracy;
|
|
i.args[0] = args[1];
|
|
}
|
|
return;
|
|
}
|
|
else if ( (args[0] > 20) && !Random[Spreadgun](0,800/args[0]) )
|
|
{
|
|
let i = Spawn("SaltImpact",level.Vec3Offset(pos,x*32));
|
|
i.angle = atan2(x.y,x.x);
|
|
i.pitch = asin(-x.z);
|
|
i.target = target;
|
|
i.special1 = (Stamina-9)/4;
|
|
i.special2 = Accuracy;
|
|
i.args[0] = args[1];
|
|
return;
|
|
}
|
|
// next beam
|
|
if ( !(special2%4) && !Random[Spreadgun](0,Stamina/2) )
|
|
Spawn("SaltLight",level.Vec3Offset(pos,x*16));
|
|
let next = Spawn("SaltBeam",level.Vec3Offset(pos,x*32));
|
|
double a = FRandom[Spreadgun](0,360), s = FRandom[Spreadgun](0,.06);
|
|
Vector3 dir = (x+y*cos(a)*s+z*sin(a)*s).unit();
|
|
next.angle = atan2(dir.y,dir.x);
|
|
next.pitch = asin(-dir.z);
|
|
next.target = target;
|
|
next.special2 = (special2+1)%10;
|
|
next.args[0] = args[0]+1;
|
|
next.args[1] = args[1];
|
|
next.SetStateLabel("TrailSpawn");
|
|
}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( !Random[Spreadgun](0,3) )
|
|
A_StartSound("spreadgun/salttrail",CHAN_VOICE,CHANF_DEFAULT,.3,4.);
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_FadeOut(.04);
|
|
if ( Random[Spreadgun](-2,args[2]/10) == 0 )
|
|
{
|
|
SWWMHandler.DoBlast(self,64,5000,target);
|
|
A_Explode(5+Accuracy,64,0);
|
|
}
|
|
args[2]++;
|
|
if ( ((special2%5) || args[2]) && !special1 ) SpreadOut();
|
|
if ( !CheckNoDelay() || (tics == -1) ) return;
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1 Bright NoDelay
|
|
{
|
|
return FindState("StarterDev")+Random[Spreadgun](0,11)*2;
|
|
}
|
|
Stop;
|
|
TrailSpawn:
|
|
XZW2 A -1 Bright
|
|
{
|
|
return FindState("TrailerDev")+Random[Spreadgun](0,11)*2;
|
|
}
|
|
Stop;
|
|
StarterDev:
|
|
#### # 25 Bright;
|
|
XZW1 B -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 C -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 D -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 E -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 F -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 G -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 H -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 I -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 J -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 K -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 L -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 M -1 Bright;
|
|
Stop;
|
|
TrailerDev:
|
|
#### # 25 Bright;
|
|
XZW2 B -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 C -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 D -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 E -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 F -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 G -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 H -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 I -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 J -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 K -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 L -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 M -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class OnFireLight : DynamicLight
|
|
{
|
|
OnFire of;
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !of || !of.victim )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Args[0] = clamp(of.Amount*4,0,255);
|
|
Args[1] = clamp(of.Amount*2,0,160);
|
|
Args[2] = clamp(of.Amount/2,0,24);
|
|
Args[3] = int(max(of.victim.default.radius,of.victim.default.height)*(of.victim.scale.x+of.victim.scale.y)*1.2+40+clamp(of.amount/5,0,120));
|
|
SetOrigin(of.Victim.Vec3Offset(0,0,of.Victim.Height/2),true);
|
|
}
|
|
}
|
|
|
|
Class OnFire : Actor
|
|
{
|
|
Actor victim, instigator, lite;
|
|
int amount, cnt, delay;
|
|
double oangle;
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !victim )
|
|
{
|
|
A_StopSound(CHAN_5);
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(victim.pos,true);
|
|
if ( victim.waterlevel > 0 )
|
|
{
|
|
if ( lite ) lite.Destroy();
|
|
amount -= int(victim.waterlevel**2);
|
|
}
|
|
if ( (victim.Health <= 0) || ((victim is 'FlamingChunk') && !victim.bMISSILE) ) amount = min(amount,100);
|
|
if ( !(level.maptime%6) )
|
|
amount--;
|
|
if ( victim.player ) amount -= int(abs(actor.deltaangle(victim.angle,oangle))/30);
|
|
oangle = victim.angle;
|
|
if ( amount < -30 )
|
|
{
|
|
A_StopSound(CHAN_5);
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( cnt > 0 ) cnt--;
|
|
else
|
|
{
|
|
cnt = min(10,30-int(29*(min(1.,amount/500.)**3.)));
|
|
if ( victim.bSHOOTABLE && (victim.Health > 0) && (amount > 0) )
|
|
{
|
|
int flg = DMG_THRUSTLESS;
|
|
if ( victim is 'Centaur' ) flg |= DMG_FOILINVUL; // you're on fire, that shield is worthless
|
|
victim.DamageMobj(self,instigator,clamp(int(amount*.1),1,30),'Fire',flg); // need to use this actor as inflictor to have a proper obituary
|
|
}
|
|
if ( !victim )
|
|
{
|
|
A_StopSound(CHAN_5);
|
|
Destroy();
|
|
return;
|
|
}
|
|
}
|
|
double mult = max(victim.radius,victim.height)/30.;
|
|
if ( victim is 'FlamingChunk' ) mult *= 20.-victim.special1*3.;
|
|
if ( delay > 0 ) delay--;
|
|
if ( (level.maptime+special1)%6 ) return;
|
|
A_SoundVolume(CHAN_5,min(1.,mult*amount/80.));
|
|
int numpt = clamp(int(Random[FlameT](2,4)*amount*.01),1,4);
|
|
numpt = int(clamp(numpt*mult**.5,1,3));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pos = victim.Vec3Offset(FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](victim.height*0.2,victim.height*0.8));
|
|
double ang = FRandom[FlameT](0,360);
|
|
double pt = FRandom[FlameT](-90,90);
|
|
if ( amount > 0 )
|
|
{
|
|
let c = victim.Spawn("OnFireTrail",pos);
|
|
c.special1 = Random[FlameT](-2,2);
|
|
c.scale *= max(.3,mult*0.5);
|
|
c.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.5,2.)*c.scale.x;
|
|
}
|
|
if ( !(i%2) )
|
|
{
|
|
let s = victim.Spawn("SWWMHalfSmoke",pos);
|
|
s.scale *= max(1.,1.6*mult);
|
|
s.alpha *= min(amount+30,100)*.01;
|
|
s.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.2,.6)*s.scale.x;
|
|
}
|
|
}
|
|
if ( amount <= 0 ) return;
|
|
// spread to nearby actors
|
|
let bt = BlockThingsIterator.Create(victim);
|
|
while ( bt.Next() )
|
|
{
|
|
let t = bt.Thing;
|
|
if ( !t || !t.bSHOOTABLE || (t.Health <= 0) || (t == victim) || ((t == instigator) && (delay > 0)) || (victim.Distance3D(t) > victim.radius+t.radius+40) || !victim.CheckSight(t) ) continue;
|
|
int amt = max(1,amount/10);
|
|
if ( IsOnFire(t) ) amt = min(5,amt);
|
|
Apply(t,instigator,amt);
|
|
}
|
|
}
|
|
|
|
static OnFire Apply( Actor victim, Actor instigator, int amount, int delay = 0 )
|
|
{
|
|
if ( amount <= 0 ) return null;
|
|
let ti = ThinkerIterator.Create("OnFire");
|
|
OnFire t;
|
|
while ( t = OnFire(ti.Next()) )
|
|
{
|
|
if ( t.victim != victim ) continue;
|
|
if ( instigator ) t.instigator = instigator;
|
|
t.amount = min(500,t.amount+amount);
|
|
t.cnt = min(t.cnt,5);
|
|
return t;
|
|
}
|
|
t = OnFire(Spawn("OnFire",victim.pos));
|
|
t.victim = victim;
|
|
t.instigator = instigator;
|
|
t.amount = min(500,amount);
|
|
t.cnt = 1;
|
|
t.special1 = Random[FlameT](0,10);
|
|
t.A_StartSound("spreadgun/flame",CHAN_5,CHANF_LOOP);
|
|
double mult = max(victim.radius,victim.height)/30.;
|
|
if ( victim is 'FlamingChunk' ) mult *= 20.-victim.special1*3.;
|
|
t.A_SoundVolume(CHAN_5,min(1.,mult*amount/80.));
|
|
// for chunks
|
|
t.delay = delay;
|
|
t.lite = Actor.Spawn("OnFireLight",victim.pos);
|
|
OnFireLight(t.lite).of = t;
|
|
t.oangle = victim.angle;
|
|
return t;
|
|
}
|
|
|
|
static bool IsOnFire( Actor victim )
|
|
{
|
|
let ti = ThinkerIterator.Create("OnFire");
|
|
OnFire t;
|
|
while ( t = OnFire(ti.Next()) )
|
|
{
|
|
if ( t.victim != victim ) continue;
|
|
return (t.amount>0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOEXTREMEDEATH;
|
|
+NOINTERACTION;
|
|
Obituary "$O_SPREADGUN_BLACK";
|
|
}
|
|
}
|
|
|
|
Class OnFireTrailLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "HellExpl";
|
|
Args 0,0,0,40;
|
|
ReactionTime 40;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
Args[0] /= 10;
|
|
Args[1] /= 10;
|
|
Args[2] /= 10;
|
|
Args[3] += 3;
|
|
if ( !target || (target.waterlevel > 0) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
}
|
|
}
|
|
|
|
Class OnFireTrail : Actor
|
|
{
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
Scale.x *= RandomPick[ExploS](-1,1);
|
|
Scale.y *= RandomPick[ExploS](-1,1);
|
|
roll = FRandom[ExploS](0,360);
|
|
}
|
|
action void A_Flame()
|
|
{
|
|
special1++;
|
|
if ( waterlevel > 0 )
|
|
vel *= .9;
|
|
else
|
|
{
|
|
vel *= .98;
|
|
vel.z += .1+.2*abs(scale.x);
|
|
}
|
|
if ( waterlevel > 0 )
|
|
{
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = (FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2));
|
|
s.vel += vel*.3;
|
|
s.alpha *= alpha*2;
|
|
s.scale *= .5+abs(scale.x)*(.5+special1/6.);
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( !Random[FlameT](0,int(40*(default.alpha-alpha))) )
|
|
{
|
|
let s = Spawn("SWWMHalfSmoke",pos);
|
|
s.vel = (FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2));
|
|
s.vel += vel*.3;
|
|
s.alpha *= alpha*1.5;
|
|
s.scale *= .5+abs(scale.x)*(.5+special1/6.);
|
|
}
|
|
}
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Speed 2;
|
|
Radius 4;
|
|
Height 4;
|
|
Alpha .6;
|
|
Scale .8;
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOFRICTION;
|
|
+SLIDESONWALLS;
|
|
+NOTELEPORT;
|
|
+FORCEXYBILLBOARD;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+DROPOFF;
|
|
+NOBLOCKMONST;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XFLM ABCDEFGHIJKLMNOPQRST 1 Bright
|
|
{
|
|
A_Flame();
|
|
A_SetScale(scale.x*0.98);
|
|
A_FadeOut(0.02);
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class FlamingChunk : Actor
|
|
{
|
|
double rollvel;
|
|
OnFire myfire;
|
|
Vector3 oldvel;
|
|
int deadtimer;
|
|
Actor lasthit;
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
|
|
Scale *= FRandom[FlameT](.8,1.2);
|
|
if ( waterlevel <= 0 ) myfire = OnFire.Apply(self,target,int(200*scale.x),special1?0:6);
|
|
frame = Random[FlameT](0,5);
|
|
}
|
|
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
|
|
{
|
|
if ( target != lasthit )
|
|
{
|
|
OnFire.Apply(target,self.target,myfire?myfire.Amount:1);
|
|
lasthit = target;
|
|
}
|
|
return damage;
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( InStateSequence(CurState,ResolveState("Death")) )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
bHITOWNER = true;
|
|
lasthit = null;
|
|
Vector3 HitNormal = -vel.unit();
|
|
F3DFloor ff;
|
|
if ( BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = CurSector.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<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = CurSector.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 ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
|
|
HitNormal *= -1;
|
|
}
|
|
else if ( BlockingMobj )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
|
|
HitNormal = diff.unit();
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
vel = .7*((vel dot HitNormal)*HitNormal*FRandom[FlameT](-1.1,-1.6)+vel);
|
|
gravity = .35;
|
|
if ( (vel.length() < 2) && (pos.z <= floorz) )
|
|
{
|
|
ClearBounce();
|
|
ExplodeMissile();
|
|
}
|
|
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
|
|
// see if we can split up
|
|
if ( (oldvel.length() > 5) && (scale.x > .1) && !Random[FlameT](0,2+special1*2) )
|
|
{
|
|
int npt = Random[FlameT](2,3);
|
|
for ( int i=0; i<npt; i++ )
|
|
{
|
|
let s = FlamingChunk(Spawn("FlamingChunk",pos));
|
|
Vector3 dir = (vel*.04+(FRandom[FlameT](-1,1),FRandom[FlameT](-1,1),FRandom[FlameT](-1,1))).unit();
|
|
s.special1 = special1+1;
|
|
s.vel = dir*vel.length()*FRandom[FlameT](.9,1.3);
|
|
s.angle = atan2(dir.y,dir.x);
|
|
s.pitch = asin(-dir.z);
|
|
s.target = target;
|
|
s.scale = scale*(1./npt);
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_BLACK";
|
|
DamageFunction 1;
|
|
DamageType 'Fire';
|
|
Scale .5;
|
|
Radius 2;
|
|
Height 2;
|
|
Speed 30;
|
|
Gravity 0.15;
|
|
PROJECTILE;
|
|
-NOGRAVITY;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+EXPLODEONWATER;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NODAMAGETHRUST;
|
|
+INTERPOLATEANGLES;
|
|
+RIPPER;
|
|
+BLOODLESSIMPACT;
|
|
+NOTELEPORT;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+USEBOUNCESTATE;
|
|
BounceFactor 1.0;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
JUNK # 1 { roll += rollvel; }
|
|
Wait;
|
|
Bounce:
|
|
JUNK # 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
Death:
|
|
JUNK # -1 { bMOVEWITHSECTOR = true; }
|
|
Stop;
|
|
Dummy:
|
|
JUNK ABCDEF -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class BallImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( swwm_extraalert ) A_AlertMonsters(500);
|
|
A_QuakeEx(3,3,3,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.3);
|
|
A_StartSound("spreadgun/ball",CHAN_VOICE);
|
|
A_SprayDecal("WallCrack",-20);
|
|
int numpt = Random[Spreadgun](5,10);
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8))).unit()*FRandom[Spreadgun](.1,1.2);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](128,192));
|
|
}
|
|
numpt = Random[Spreadgun](4,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Spreadgun](4,8);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class TheBall : Actor
|
|
{
|
|
double heat;
|
|
int deadtimer;
|
|
Vector3 oldvel;
|
|
Actor lasthit;
|
|
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_PURPLE";
|
|
PROJECTILE;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+CANBOUNCEWATER;
|
|
+USEBOUNCESTATE;
|
|
+DONTBOUNCEONSKY;
|
|
+NODAMAGETHRUST;
|
|
-NOGRAVITY;
|
|
Speed 80;
|
|
Gravity 0.1;
|
|
BounceFactor 1.0;
|
|
Radius 2;
|
|
Height 2;
|
|
}
|
|
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( special1 == 1 ) return StringTable.Localize("$O_WALLBUSTER_PURPLE");
|
|
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
|
}
|
|
override int SpecialMissileHit( Actor victim )
|
|
{
|
|
if ( (vel.length() <= 5) || ((victim == target) && !bHITOWNER) || (victim == lasthit) || (!victim.bSHOOTABLE && !victim.bSOLID) )
|
|
return 1;
|
|
// check if we should rip or bounce
|
|
// girthitude
|
|
double girth = (victim.radius+victim.height)/2.*max(50,victim.mass)*(victim.health/double(victim.GetSpawnHealth()));
|
|
// how hard this damn thing is going to slam
|
|
double slamforce = vel.length()*350.+heat*120;
|
|
int dmg = int(vel.length()*4.2+heat*80);
|
|
bool is_schutt = victim.bSHOOTABLE;
|
|
// critical hit!
|
|
bool crit = false;
|
|
if ( is_schutt && !Random[Spreadgun](0,9) )
|
|
{
|
|
Spawn("SWWMItemFog",pos);
|
|
int whichclonk = Random[Spreadgun](1,11);
|
|
String snd = String.Format("misc/clonk%d",whichclonk);
|
|
A_AlertMonsters(2500);
|
|
A_StartSound(snd,CHAN_VOICE,CHANF_OVERLAP,1.,.2);
|
|
victim.A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:1.);
|
|
victim.A_StartSound(snd,CHAN_DAMAGE,CHANF_OVERLAP,1.,.2);
|
|
slamforce *= 4;
|
|
dmg *= 4;
|
|
vel *= 1.1;
|
|
let numpt = Random[Spreadgun](20,30);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](1,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.scale *= 3.;
|
|
s.alpha *= .2;
|
|
s.vel = pvel;
|
|
}
|
|
crit = true;
|
|
}
|
|
SWWMHandler.DoKnockback(victim,vel.unit(),slamforce);
|
|
bool bleeds = (victim && !victim.bINVULNERABLE && !victim.bNOBLOOD && is_schutt);
|
|
dmg = victim.DamageMobj(self,target,dmg,'Concussion',crit?(DMG_THRUSTLESS|DMG_FOILINVUL):DMG_THRUSTLESS); // crits ignore invulnerability
|
|
Vector3 dir = -vel.unit();
|
|
// slam jam
|
|
if ( bleeds )
|
|
{
|
|
A_StartSound("spreadgun/ballf",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
if ( victim )
|
|
{
|
|
victim.A_StartSound("spreadgun/ballf",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
victim.TraceBleed(dmg,self);
|
|
victim.SpawnBlood(pos,atan2(dir.y,dir.x),dmg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
if ( victim ) victim.A_StartSound("spreadgun/ball",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
if ( vel.length() > 15. )
|
|
{
|
|
let s = Spawn("BallImpact",pos);
|
|
s.angle = atan2(dir.y,dir.x);
|
|
s.pitch = asin(-dir.z);
|
|
}
|
|
}
|
|
if ( crit )
|
|
{
|
|
SWWMHandler.DoBlast(self,150,25000,target);
|
|
A_Explode(dmg/2,150,0);
|
|
}
|
|
// only rip shootables
|
|
if ( (slamforce > girth) && is_schutt )
|
|
{
|
|
vel *= .7;
|
|
return 1;
|
|
}
|
|
// force bounce
|
|
BlockingMobj = victim;
|
|
A_HandleBounce();
|
|
lasthit = victim;
|
|
// pretend to pass through
|
|
return 1;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("pusher/fly",CHAN_WEAPON,CHANF_LOOPING,.6,3.,2.);
|
|
heat = 1.;
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( InStateSequence(CurState,ResolveState("Death")) )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
heat -= 0.004+0.0004*vel.length();
|
|
A_SoundVolume(CHAN_WEAPON,vel.length()/75.);
|
|
if ( heat <= 0 ) return;
|
|
let s = Spawn("SWWMHalfSmoke",pos);
|
|
s.alpha *= heat;
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
bHITOWNER = true;
|
|
lasthit = null;
|
|
Vector3 HitNormal = -vel.unit();
|
|
F3DFloor ff;
|
|
LineTracer faketracer = new("LineTracer");
|
|
if ( BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff )
|
|
{
|
|
faketracer.Results.ffloor = ff;
|
|
HitNormal = -ff.top.Normal;
|
|
}
|
|
else HitNormal = BlockingFloor.floorplane.Normal;
|
|
faketracer.Results.HitType = TRACE_HitFloor;
|
|
faketracer.Results.HitSector = BlockingFloor;
|
|
}
|
|
else if ( BlockingCeiling )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff )
|
|
{
|
|
faketracer.Results.ffloor = ff;
|
|
HitNormal = -ff.bottom.Normal;
|
|
}
|
|
else HitNormal = BlockingCeiling.ceilingplane.Normal;
|
|
faketracer.Results.HitType = TRACE_HitCeiling;
|
|
faketracer.Results.HitSector = BlockingCeiling;
|
|
}
|
|
else if ( BlockingLine )
|
|
{
|
|
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
int wside = SWWMUtility.PointOnLineSide(pos.xy,BlockingLine);
|
|
if ( !wside ) HitNormal *= -1;
|
|
if ( (oldvel.length() > 15) && swwm_balluse )
|
|
{
|
|
int locknum = SWWMUtility.GetLineLock(BlockingLine);
|
|
// remotely activate unlocked lines (that aren't exits)
|
|
if ( (!locknum || (target && target.CheckKeys(locknum,false,true))) && !SWWMUtility.IsExitLine(BlockingLine) )
|
|
BlockingLine.RemoteActivate(target,wside,SPAC_Use,pos);
|
|
}
|
|
faketracer.Results.HitType = TRACE_HitWall;
|
|
faketracer.Results.HitLine = BlockingLine;
|
|
faketracer.Results.Side = wside;
|
|
faketracer.Results.Tier = TIER_Middle;
|
|
// guess the tier hit
|
|
if ( BlockingLine.sidedef[1] )
|
|
{
|
|
double ceil = BlockingLine.sidedef[!wside].sector.ceilingplane.ZAtPoint(pos.xy);
|
|
double flor = BlockingLine.sidedef[!wside].sector.floorplane.ZAtPoint(pos.xy);
|
|
if ( pos.z >= ceil ) faketracer.Results.Tier = TIER_Upper;
|
|
else if ( (pos.z+Height) <= flor ) faketracer.Results.Tier = TIER_Lower;
|
|
}
|
|
}
|
|
else if ( BlockingMobj )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
|
|
HitNormal = diff.unit();
|
|
faketracer.Results.HitType = TRACE_HitActor;
|
|
}
|
|
// send the needed data for a bust
|
|
if ( special1 == 1 )
|
|
{
|
|
int dmg = int(oldvel.length()*4.2+heat*80);
|
|
BusterWall.Bust(faketracer.Results,dmg,target,oldvel.unit(),pos.z+Height/2.);
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
double bcefact = .9;
|
|
if ( BlockingMobj )
|
|
{
|
|
bcefact *= .7;
|
|
if ( !BlockingMobj.bINVULNERABLE && !BlockingMobj.bNOBLOOD )
|
|
bcefact *= .6;
|
|
}
|
|
vel = (vel dot HitNormal)*HitNormal*FRandom[Spreadgun](-1.8,-1.)+vel;
|
|
vel += (FRandom[Spreadgun](-.4,.4),FRandom[Spreadgun](-.4,.4),FRandom[Spreadgun](-.4,.4));
|
|
vel *= bcefact;
|
|
// slam jam
|
|
if ( !BlockingMobj )
|
|
{
|
|
A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,max(0.,(vel.length()/30.-.2))**.5);
|
|
if ( vel.length() > 15 )
|
|
{
|
|
let s = Spawn("BallImpact",pos);
|
|
s.angle = atan2(HitNormal.y,HitNormal.x);
|
|
s.pitch = asin(-HitNormal.z);
|
|
}
|
|
}
|
|
gravity = .35;
|
|
if ( (vel.length() < 5) && (pos.z <= floorz) )
|
|
{
|
|
ClearBounce();
|
|
ExplodeMissile();
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
Bounce:
|
|
XZW1 A 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
Death:
|
|
XZW1 A -1
|
|
{
|
|
bMOVEWITHSECTOR = true;
|
|
A_StopSound(CHAN_WEAPON);
|
|
}
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class GExploLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
ReactionTime 80;
|
|
Args 0,0,0,220;
|
|
}
|
|
}
|
|
|
|
Class GExploLight2 : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
ReactionTime 60;
|
|
Args 0,0,0,120;
|
|
}
|
|
}
|
|
|
|
Class GoldenImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
DamageType "Explodium";
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
Scale 8.;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
+FOILINVUL;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_AlertMonsters(40000);
|
|
SWWMHandler.DoBlast(self,500,40000);
|
|
A_Explode(7777,600,fulldamagedistance:500);
|
|
A_QuakeEx(9,9,9,40,0,5000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:500,rollintensity:1.5);
|
|
A_StartSound("spreadgun/goldexpl",CHAN_VOICE,attenuation:.3);
|
|
A_StartSound("spreadgun/goldexpl",CHAN_WEAPON,attenuation:.15);
|
|
A_SprayDecal("WumboScorch",-172);
|
|
Scale *= FRandom[ExploS](0.8,1.1);
|
|
Scale.x *= RandomPick[ExploS](-1,1);
|
|
Scale.y *= RandomPick[ExploS](-1,1);
|
|
int numpt = Random[ExploS](10,20);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,9);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](64,224)+Color(30,25,0));
|
|
s.special1 = Random[ExploS](3,5);
|
|
s.scale *= 3.;
|
|
s.alpha *= .8;
|
|
}
|
|
numpt = Random[ExploS](8,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[ExploS](6,16);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,24);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[ExploS](12,24);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](8,40);
|
|
let s = Spawn("FancyConfetti",pos);
|
|
s.scale *= 3.;
|
|
s.bAMBUSH = true;
|
|
s.vel = pvel;
|
|
}
|
|
Spawn("GExploLight",pos);
|
|
}
|
|
action void A_GoldSpread()
|
|
{
|
|
special1++;
|
|
if ( (special1%3) || (special1 > 20) ) return;
|
|
FLineTraceData d;
|
|
Vector3 HitNormal;
|
|
Vector3 origin;
|
|
double ang, pt;
|
|
for ( int i=0; i<6; i++ )
|
|
{
|
|
double totaldist = 30+(special1**2.)*1.2;
|
|
ang = FRandom[ExploS](0,360);
|
|
pt = FRandom[ExploS](-90,90);
|
|
origin = pos;
|
|
while ( totaldist > 0 )
|
|
{
|
|
LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
|
|
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;
|
|
}
|
|
totaldist -= d.Distance;
|
|
if ( totaldist > 0 )
|
|
{
|
|
Vector3 bounced = d.HitDir-(2*hitnormal*(d.HitDir dot HitNormal));
|
|
ang = atan2(bounced.y,bounced.x);
|
|
pt = asin(-bounced.z);
|
|
origin = d.HitLocation+hitnormal;
|
|
}
|
|
}
|
|
let p = Spawn("GoldenSubImpact",d.HitLocation+hitnormal*4);
|
|
p.angle = atan2(hitnormal.y,hitnormal.x);
|
|
p.pitch = asin(-hitnormal.z);
|
|
p.target = target;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XEX1 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ[[\\]] 1 Bright A_GoldSpread();
|
|
TNT1 A 1
|
|
{
|
|
A_GoldSpread();
|
|
if ( special1 > 20 ) Destroy();
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class GoldenSubImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
DamageType "Explodium";
|
|
RenderStyle "Add";
|
|
Scale 6.;
|
|
Alpha .8;
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
+FOILINVUL;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SWWMHandler.DoBlast(self,300,30000);
|
|
A_Explode(777,400,fulldamagedistance:300);
|
|
A_QuakeEx(7,7,7,20,0,2000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollintensity:.8);
|
|
A_SprayDecal("BigScorch",-172);
|
|
Scale *= FRandom[ExploS](0.8,1.1);
|
|
Scale.x *= RandomPick[ExploS](-1,1);
|
|
Scale.y *= RandomPick[ExploS](-1,1);
|
|
int numpt = Random[ExploS](3,8);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,9);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](64,224)+Color(30,25,0));
|
|
s.special1 = Random[ExploS](2,3);
|
|
s.scale *= 2.2;
|
|
s.alpha *= .5;
|
|
}
|
|
numpt = Random[ExploS](4,6);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[ExploS](2,3);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,18);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[ExploS](2,5);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](8,24);
|
|
let s = Spawn("FancyConfetti",pos);
|
|
s.scale *= 2.;
|
|
s.bAMBUSH = true;
|
|
s.vel = pvel;
|
|
}
|
|
Spawn("GExploLight2",pos);
|
|
}
|
|
action void A_GoldSubSpread()
|
|
{
|
|
special1++;
|
|
if ( (special1%2) || (special1 > 10) ) return;
|
|
FLineTraceData d;
|
|
Vector3 HitNormal;
|
|
Vector3 origin;
|
|
double ang, pt;
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
double totaldist = 20+(special1**2.)*.8;
|
|
ang = FRandom[ExploS](0,360);
|
|
pt = FRandom[ExploS](-90,90);
|
|
origin = pos;
|
|
while ( totaldist > 0 )
|
|
{
|
|
LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
|
|
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;
|
|
}
|
|
totaldist -= d.Distance;
|
|
if ( totaldist > 0 )
|
|
{
|
|
Vector3 bounced = d.HitDir-(2*hitnormal*(d.HitDir dot HitNormal));
|
|
ang = atan2(bounced.y,bounced.x);
|
|
pt = asin(-bounced.z);
|
|
origin = d.HitLocation+hitnormal;
|
|
}
|
|
}
|
|
let p = Spawn("GoldenSubSubImpact",d.HitLocation+hitnormal*4);
|
|
p.angle = atan2(hitnormal.y,hitnormal.x);
|
|
p.pitch = asin(-hitnormal.z);
|
|
p.target = target;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XEX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright A_GoldSubSpread();
|
|
TNT1 A 1
|
|
{
|
|
A_GoldSubSpread();
|
|
if ( special1 > 10 ) Destroy();
|
|
}
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class GoldenSubSubImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
DamageType "Explodium";
|
|
RenderStyle "Add";
|
|
Scale 3.;
|
|
Alpha .6;
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
+FOILINVUL;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SWWMHandler.DoBlast(self,100,20000);
|
|
A_Explode(77,200,fulldamagedistance:100);
|
|
A_QuakeEx(4,4,4,15,0,1000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollintensity:.4);
|
|
A_SprayDecal("Scorch",-172);
|
|
Scale *= FRandom[ExploS](0.8,1.1);
|
|
Scale.x *= RandomPick[ExploS](-1,1);
|
|
Scale.y *= RandomPick[ExploS](-1,1);
|
|
int numpt = Random[ExploS](1,2);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](64,224)+Color(30,25,0));
|
|
s.special1 = Random[ExploS](0,2);
|
|
s.scale *= 1.6;
|
|
s.alpha *= .2;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XEX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class Spreadgun : SWWMWeapon
|
|
{
|
|
bool fired; // shell was used
|
|
Class<Ammo> loadammo, nextammo; // currently loaded shell, next shell to load
|
|
|
|
transient ui TextureID WeaponBox, AmmoIcon[7], LoadedIcon[7];
|
|
transient ui Font TewiFont;
|
|
|
|
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( loadammo is 'RedShell' ) return StringTable.Localize("$O_SPREADGUN_RED");
|
|
if ( loadammo is 'GreenShell' ) return StringTable.Localize("$O_SPREADGUN_GREEN");
|
|
if ( loadammo is 'WhiteShell' ) return StringTable.Localize("$O_SPREADGUN_WHITE");
|
|
if ( loadammo is 'BlueShell' ) return StringTable.Localize("$O_SPREADGUN_BLUE");
|
|
if ( loadammo is 'BlackShell' ) return StringTable.Localize("$O_SPREADGUN_BLACK");
|
|
if ( loadammo is 'PurpleShell' ) return StringTable.Localize("$O_SPREADGUN_PURPLE");
|
|
if ( loadammo is 'GoldShell' ) return StringTable.Localize("$O_SPREADGUN_GOLD");
|
|
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
|
}
|
|
|
|
override void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
if ( !WeaponBox )
|
|
{
|
|
WeaponBox = TexMan.CheckForTexture("graphics/HUD/SpreadgunDisplay.png",TexMan.Type_Any);
|
|
AmmoIcon[0] = TexMan.CheckForTexture("graphics/HUD/RedShell.png",TexMan.Type_Any);
|
|
AmmoIcon[1] = TexMan.CheckForTexture("graphics/HUD/GreenShell.png",TexMan.Type_Any);
|
|
AmmoIcon[2] = TexMan.CheckForTexture("graphics/HUD/WhiteShell.png",TexMan.Type_Any);
|
|
AmmoIcon[3] = TexMan.CheckForTexture("graphics/HUD/BlueShell.png",TexMan.Type_Any);
|
|
AmmoIcon[4] = TexMan.CheckForTexture("graphics/HUD/BlackShell.png",TexMan.Type_Any);
|
|
AmmoIcon[5] = TexMan.CheckForTexture("graphics/HUD/PurpleShell.png",TexMan.Type_Any);
|
|
AmmoIcon[6] = TexMan.CheckForTexture("graphics/HUD/GoldShell.png",TexMan.Type_Any);
|
|
LoadedIcon[0] = TexMan.CheckForTexture("graphics/HUD/LoadedRedShell.png",TexMan.Type_Any);
|
|
LoadedIcon[1] = TexMan.CheckForTexture("graphics/HUD/LoadedGreenShell.png",TexMan.Type_Any);
|
|
LoadedIcon[2] = TexMan.CheckForTexture("graphics/HUD/LoadedWhiteShell.png",TexMan.Type_Any);
|
|
LoadedIcon[3] = TexMan.CheckForTexture("graphics/HUD/LoadedBlueShell.png",TexMan.Type_Any);
|
|
LoadedIcon[4] = TexMan.CheckForTexture("graphics/HUD/LoadedBlackShell.png",TexMan.Type_Any);
|
|
LoadedIcon[5] = TexMan.CheckForTexture("graphics/HUD/LoadedPurpleShell.png",TexMan.Type_Any);
|
|
LoadedIcon[6] = TexMan.CheckForTexture("graphics/HUD/LoadedGoldShell.png",TexMan.Type_Any);
|
|
}
|
|
if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded');
|
|
Screen.DrawTexture(WeaponBox,false,bx-54,by-43,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
|
|
int ox = 6;
|
|
int oy = 11;
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
Screen.DrawTexture(AmmoIcon[i],false,bx-ox,by-oy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(types[i]==nextammo)?Color(0,0,0,0):Color(128,0,0,0));
|
|
String astr = String.Format("%3d",Owner.CountInv(types[i]));
|
|
Screen.DrawText(TewiFont,Font.CR_FIRE,bx-ox-(TewiFont.StringWidth(astr)+1),by-oy-2,astr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(types[i]==nextammo)?Color(0,0,0,0):Color(128,0,0,0));
|
|
oy += 10;
|
|
if ( i == 3 )
|
|
{
|
|
oy = 21;
|
|
ox = 33;
|
|
}
|
|
}
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( loadammo != types[i] ) continue;
|
|
Screen.DrawTexture(LoadedIcon[i],false,bx-48,by-8,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fired?Color(128,0,0,0):Color(0,0,0,0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
override bool ReportHUDAmmo()
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
for ( int i=0; i<7; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
|
|
return !fired;
|
|
}
|
|
|
|
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
if ( (firemode == PrimaryFire) || (firemode == AltFire) )
|
|
{
|
|
if ( !fired ) return true;
|
|
for ( int i=0; i<7; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
|
|
return false;
|
|
}
|
|
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
|
|
}
|
|
|
|
override bool UsesAmmo( Class<Ammo> kind )
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
for ( int i=0; i<7; i++ ) if ( kind is types[i] ) return true;
|
|
return false;
|
|
}
|
|
|
|
action void A_SelectUnloadState()
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
static const statelabel primedstates[] = {"UnloadRed", "UnloadGreen", "UnloadWhite", "UnloadBlue", "UnloadBlack", "UnloadPurple", "UnloadGold"};
|
|
static const statelabel firedstates[] = {"UnloadRedFired", "UnloadGreenFired", "UnloadWhiteFired", "UnloadBlueFired", "UnloadBlackFired", "UnloadPurpleFired", "UnloadGoldFired"};
|
|
int amidx = 0;
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( invoker.loadammo != types[i] ) continue;
|
|
amidx = i;
|
|
break;
|
|
}
|
|
if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx]));
|
|
else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
|
|
A_Overlay(-9999,"UnloadDummy");
|
|
A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
|
|
action void A_SelectLoadState()
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
static const statelabel primedstates[] = {"LoadRed", "LoadGreen", "LoadWhite", "LoadBlue", "LoadBlack", "LoadPurple", "LoadGold"};
|
|
static const statelabel firedstates[] = {"LoadRedFired", "LoadGreenFired", "LoadWhiteFired", "LoadBlueFired", "LoadBlackFired", "LoadPurpleFired", "LoadGoldFired"};
|
|
int amidx = 0;
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( invoker.nextammo != types[i] ) continue;
|
|
amidx = i;
|
|
break;
|
|
}
|
|
if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx]));
|
|
else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
|
|
if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
|
|
{
|
|
let amo = FindInventory(invoker.nextammo);
|
|
if ( amo && (amo.Amount > 0) ) amo.Amount--;
|
|
}
|
|
A_Overlay(-9999,"LoadDummy");
|
|
}
|
|
|
|
action void A_DropShell()
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
static const Class<Actor> casetypes[] = {"RedShellCasing","GreenShellCasing","WhiteShellCasing","BlueShellCasing","BlackShellCasing","PurpleShellCasing","GoldShellCasing"};
|
|
if ( !invoker.fired )
|
|
{
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( invoker.loadammo != types[i] ) continue;
|
|
let amo = FindInventory(types[i]);
|
|
if ( !amo )
|
|
{
|
|
amo = Inventory(Spawn(types[i]));
|
|
amo.AttachToOwner(self);
|
|
amo.Amount = 0;
|
|
}
|
|
if ( amo.Amount < amo.MaxAmount ) amo.Amount++;
|
|
else if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
|
|
Spawn(types[i],Vec3Angle(5,angle,height/2));
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( invoker.loadammo != types[i] ) continue;
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-10*z);
|
|
let c = Spawn(casetypes[i],origin);
|
|
c.angle = angle;
|
|
c.pitch = pitch;
|
|
c.vel = x*FRandom[Junk](-.2,.2)+y*FRandom[Junk](-.2,.2)-(0,0,FRandom[Junk](2,3));
|
|
c.vel += vel*.5;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
action void ProcessTraceHit( SpreadgunTracer t, Vector3 origin, Vector3 dir, int dmg, double mm, Class<Actor> impact = "SpreadImpact", int bc = 1, bool large = false )
|
|
{
|
|
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
|
|
{
|
|
t.ShootThroughList[i].Activate(self,0,SPAC_PCross);
|
|
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
|
|
}
|
|
for ( int i=0; i<t.WaterHitList.Size(); i++ )
|
|
{
|
|
let b = Spawn(large?"InvisibleSplasher":"SmolInvisibleSplasher",t.WaterHitList[i].hitpos);
|
|
b.A_CheckTerrain();
|
|
}
|
|
for ( int i=5; i<t.Results.Distance; i+=10 )
|
|
{
|
|
if ( !Random[Boolet](0,bc) ) continue;
|
|
let b = Actor.Spawn("SWWMBubble",level.Vec3Offset(origin,dir*i));
|
|
b.Scale *= FRandom[Boolet](.1,.3);
|
|
}
|
|
for ( int i=0; i<t.HitList.Size(); i++ )
|
|
{
|
|
int realdmg = dmg?dmg:t.HitList[i].HitDamage;
|
|
SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,invoker,self,'shot',!large);
|
|
SWWMHandler.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),mm*FRandom[Spreadgun](0.4,1.2));
|
|
if ( t.HitList[i].HitActor.bNOBLOOD || t.HitList[i].HitActor.bINVULNERABLE )
|
|
{
|
|
let p = Spawn(impact,t.HitList[i].HitLocation);
|
|
p.angle = atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180;
|
|
p.pitch = asin(t.HitList[i].x.z);
|
|
}
|
|
else
|
|
{
|
|
t.HitList[i].HitActor.TraceBleed(realdmg,self);
|
|
t.HitList[i].HitActor.SpawnBlood(t.HitList[i].HitLocation,atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180,realdmg);
|
|
if ( large ) t.HitList[i].HitActor.A_StartSound("spreadgun/slugf",CHAN_DAMAGE,CHANF_OVERLAP,1.,2.);
|
|
else t.HitList[i].HitActor.A_StartSound("spreadgun/pelletf",CHAN_DAMAGE,CHANF_OVERLAP,.4,4.);
|
|
}
|
|
}
|
|
if ( (t.Results.HitType != TRACE_HitNone) && (t.Results.HitType != TRACE_HasHitSky) && (t.Results.HitType != TRACE_HitActor) )
|
|
{
|
|
Vector3 hitnormal = -t.Results.HitVector;
|
|
if ( t.Results.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.top.Normal;
|
|
else hitnormal = t.Results.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.bottom.Normal;
|
|
else hitnormal = t.Results.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitWall )
|
|
{
|
|
hitnormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
|
|
if ( !t.Results.Side ) hitnormal *= -1;
|
|
}
|
|
let p = Spawn(impact,t.Results.HitPos+hitnormal*4);
|
|
p.angle = atan2(hitnormal.y,hitnormal.x);
|
|
p.pitch = asin(-hitnormal.z);
|
|
if ( t.Results.HitLine ) t.Results.HitLine.RemoteActivate(self,t.Results.Side,SPAC_Impact,t.Results.HitPos);
|
|
}
|
|
}
|
|
|
|
action void A_FireShell()
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
static const statelabel flashes[] = {"FlashRed","FlashGreen","FlashWhite","FlashBlue","FlashBlack","FlashPurple","FlashGold"};
|
|
static const String sounds[] = {"spreadgun/redfire","spreadgun/greenfire","spreadgun/whitefire","spreadgun/bluefire","spreadgun/blackfire","spreadgun/purplefire","spreadgun/goldfire"};
|
|
static const int louds[] = {800,1000,1100,1200,1400,600,2500};
|
|
static const int quakes[] = {3,4,2,4,3,1,6};
|
|
static const Color cols[] = {Color(40,255,192,64),Color(36,255,192,80),Color(64,255,160,32),Color(48,32,176,255),Color(72,255,128,16),Color(24,255,224,96),Color(96,255,224,16)};
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( invoker.loadammo != types[i] ) continue;
|
|
A_SWWMFlash(flashes[i]);
|
|
A_StartSound(sounds[i],CHAN_WEAPON,CHANF_OVERLAP,attenuation:.6);
|
|
A_AlertMonsters(louds[i]);
|
|
A_QuakeEx(quakes[i],quakes[i],quakes[i],9,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.2*quakes[i]);
|
|
A_ZoomFactor(1.-quakes[i]*.04,ZOOM_INSTANT);
|
|
A_ZoomFactor(1.);
|
|
A_PlayerFire();
|
|
SWWMHandler.DoFlash(self,cols[i],5);
|
|
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-2*z);
|
|
Vector3 x2, y2, z2;
|
|
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
|
|
double a, s;
|
|
Vector3 dir;
|
|
SpreadgunTracer st;
|
|
SpreadSlugTracer sst;
|
|
switch ( i )
|
|
{
|
|
case 1:
|
|
sst = new("SpreadSlugTracer");
|
|
sst.ignoreme = self;
|
|
sst.penetration = 250.;
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.01);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
sst.hitlist.Clear();
|
|
sst.shootthroughlist.Clear();
|
|
sst.waterhitlist.Clear();
|
|
sst.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
|
|
ProcessTraceHit(sst,origin,dir,0,12000,"SlugImpact",1,true);
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= 0.5;
|
|
}
|
|
for ( int i=0; i<6; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.scale *= .8;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
|
|
}
|
|
for ( int i=0; i<10; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .2;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,25000.);
|
|
break;
|
|
case 2:
|
|
for ( int j=0; j<9; j++ )
|
|
{
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.2);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
let p = Spawn("DragonBreathArm",origin);
|
|
p.target = self;
|
|
p.angle = atan2(dir.y,dir.x);
|
|
p.pitch = asin(-dir.z);
|
|
}
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= .2;
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= .9;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<15; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,13000.);
|
|
break;
|
|
case 3:
|
|
for ( int j=0; j<8; j++ )
|
|
{
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.8);
|
|
let b = Spawn("SaltBeam",level.Vec3Offset(origin,y*cos(a)*s+z*sin(a)*s));
|
|
b.target = self;
|
|
b.angle = atan2(x2.y,x2.x);
|
|
b.pitch = asin(-x2.z);
|
|
}
|
|
for ( int i=0; i<9; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,3,4)*Random[Spreadgun](32,63));
|
|
s.A_SetRenderStyle(.2,STYLE_AddShaded);
|
|
}
|
|
for ( int i=0; i<16; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= .9;
|
|
s.SetShade(Color(1,3,4)*Random[Spreadgun](32,63));
|
|
s.A_SetRenderStyle(.3,STYLE_AddShaded);
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,23000.);
|
|
break;
|
|
case 4:
|
|
for ( int j=0; j<10; j++ )
|
|
{
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.2);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
let p = Spawn("FlamingChunk",origin);
|
|
p.target = self;
|
|
p.angle = atan2(dir.y,dir.x);
|
|
p.pitch = asin(-dir.z);
|
|
p.vel = dir*p.speed*FRandom[Spreadgun](.9,1.3);
|
|
}
|
|
for ( int i=0; i<12; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](48,128));
|
|
s.alpha *= .2;
|
|
}
|
|
for ( int i=0; i<30; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= 1.3;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](48,128));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,12.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,12.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,14000.);
|
|
break;
|
|
case 5:
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.03);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
let b = Spawn("TheBall",origin);
|
|
b.target = self;
|
|
b.angle = atan2(dir.y,dir.x);
|
|
b.pitch = asin(-dir.z);
|
|
b.vel = dir*b.speed;
|
|
for ( int i=0; i<4; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= 0.4;
|
|
}
|
|
for ( int i=0; i<8; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.scale *= .6;
|
|
s.alpha *= .25;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
|
|
}
|
|
for ( int i=0; i<8; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .2;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,9500.);
|
|
break;
|
|
case 6:
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.01);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
FLineTraceData d;
|
|
LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION|TRF_NOSKY,origin.z,origin.x,origin.y,d);
|
|
SWWMBulletTrail.DoTrail(self,origin,dir,10000,2,true);
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
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("SlugImpact",d.HitLocation+hitnormal);
|
|
p.angle = atan2(hitnormal.y,hitnormal.x);
|
|
p.pitch = asin(-hitnormal.z);
|
|
if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation);
|
|
let b = Spawn("GoldenImpact",d.HitLocation+hitnormal*4.);
|
|
b.angle = atan2(hitnormal.y,hitnormal.x);
|
|
b.pitch = asin(-hitnormal.z);
|
|
b.target = self;
|
|
}
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= 0.5;
|
|
}
|
|
for ( int i=0; i<6; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.scale *= .8;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
|
|
}
|
|
for ( int i=0; i<10; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .2;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<50; i++ )
|
|
{
|
|
let s = Spawn("FancyConfetti",origin);
|
|
s.bAMBUSH = true;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](1.,20.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,30000.);
|
|
break;
|
|
default:
|
|
st = new("SpreadgunTracer");
|
|
st.ignoreme = self;
|
|
for ( int j=0; j<30; j++ )
|
|
{
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.22);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
st.hitlist.Clear();
|
|
st.shootthroughlist.Clear();
|
|
st.waterhitlist.Clear();
|
|
st.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
|
|
ProcessTraceHit(st,origin,dir,6,7000,bc:5);
|
|
}
|
|
for ( int i=0; i<9; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= .2;
|
|
}
|
|
for ( int i=0; i<16; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= .9;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,20000.);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP);
|
|
invoker.fired = true;
|
|
}
|
|
|
|
action void A_LoadShell()
|
|
{
|
|
A_StartSound("spreadgun/shellin",CHAN_WEAPON,CHANF_OVERLAP);
|
|
invoker.loadammo = invoker.nextammo;
|
|
}
|
|
|
|
action void A_Prime()
|
|
{
|
|
if ( invoker.fired )
|
|
{
|
|
A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP);
|
|
invoker.fired = false;
|
|
}
|
|
}
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
Super.AttachToOwner(other);
|
|
if ( !loadammo ) loadammo = "RedShell";
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
Ammo a = Ammo(other.FindInventory(types[i]));
|
|
if ( !a ) continue;
|
|
nextammo = types[i];
|
|
return;
|
|
}
|
|
nextammo = AmmoType1;
|
|
}
|
|
|
|
action void A_SwitchAmmoType( bool rev = false )
|
|
{
|
|
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
|
|
int cur = 0, next = 0;
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
if ( invoker.nextammo != types[i] ) continue;
|
|
cur = i;
|
|
break;
|
|
}
|
|
int ridx = -1;
|
|
if ( rev )
|
|
{
|
|
// check backwards from what we currently had
|
|
for ( int i=cur; i>=0; i-- )
|
|
{
|
|
if ( CountInv(types[i]) <= 0 ) continue;
|
|
ridx = i;
|
|
break;
|
|
}
|
|
if ( ridx == -1 )
|
|
{
|
|
// check forwards instead, but avoid golden shells
|
|
for ( int i=0; i<6; i++ )
|
|
{
|
|
if ( CountInv(types[i]) <= 0 ) continue;
|
|
ridx = i;
|
|
break;
|
|
}
|
|
}
|
|
if ( ridx != -1 ) next = ridx;
|
|
}
|
|
else
|
|
{
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
ridx = (i+cur+1)%7;
|
|
if ( CountInv(types[ridx]) <= 0 ) continue;
|
|
next = ridx;
|
|
break;
|
|
}
|
|
}
|
|
if ( invoker.nextammo != types[next] ) A_StartSound("misc/invchange",CHAN_WEAPONEXTRA,CHANF_UI|CHANF_LOCAL);
|
|
invoker.nextammo = types[next];
|
|
A_WeaponReady(WRF_NOFIRE);
|
|
}
|
|
|
|
action void A_AltHold()
|
|
{
|
|
A_WeaponReady(WRF_NOFIRE);
|
|
if ( player.cmd.buttons&BT_ALTATTACK ) return;
|
|
if ( !invoker.fired ) player.SetPSPrite(PSP_WEAPON,invoker.FindState("Ready"));
|
|
else player.SetPSPrite(PSP_WEAPON,invoker.FindState("ReadyFired"));
|
|
}
|
|
|
|
override void ModifyDropAmount( int dropamount )
|
|
{
|
|
Super.ModifyDropAmount(dropamount);
|
|
// toss some ammo while we're at it
|
|
if ( Random[Spreadgun](0,1) )
|
|
A_DropItem(Random[Spreadgun](0,2)?"RedShell":"GreenShell",Random[Spreadgun](1,2));
|
|
}
|
|
|
|
Default
|
|
{
|
|
Tag "$T_SPREADGUN";
|
|
Inventory.PickupMessage "$I_SPREADGUN";
|
|
Obituary "$O_SPREADGUN";
|
|
Inventory.Icon "graphics/HUD/Icons/W_Spreadgun.png";
|
|
Weapon.UpSound "spreadgun/select";
|
|
Weapon.SlotNumber 3;
|
|
Weapon.SelectionOrder 2400;
|
|
Weapon.AmmoType1 "RedShell";
|
|
Weapon.AmmoGive1 1;
|
|
Stamina 15000;
|
|
+SWWMWEAPON.NOFIRSTGIVE;
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
Deselect:
|
|
XZW2 A 1
|
|
{
|
|
A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP);
|
|
return A_JumpIf(invoker.fired,"DeselectFired");
|
|
}
|
|
XZW2 BCDEFGHI 1;
|
|
XZW2 I -1 A_FullLower();
|
|
Stop;
|
|
DeselectFired:
|
|
XZW2 Z 1;
|
|
XZW3 ABCDEFGH 1;
|
|
XZW3 H -1 A_FullLower();
|
|
Stop;
|
|
Select:
|
|
XZW2 I 1
|
|
{
|
|
A_FullRaise();
|
|
return A_JumpIf(invoker.fired,"SelectFired");
|
|
}
|
|
XZW2 JKLMNOPQ 1;
|
|
Goto Ready;
|
|
SelectFired:
|
|
XZW3 HIJKLMNOP 1;
|
|
Goto ReadyFired;
|
|
Ready:
|
|
XZW2 A 1
|
|
{
|
|
if ( CountInv(invoker.nextammo) <= 0 ) A_SwitchAmmoType(true);
|
|
int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1;
|
|
if ( invoker.nextammo && (CountInv(invoker.nextammo) > 0) && (invoker.loadammo != invoker.nextammo) )
|
|
flg |= WRF_ALLOWRELOAD;
|
|
A_WeaponReady(flg);
|
|
}
|
|
Wait;
|
|
ReadyFired:
|
|
XZW2 Z 1
|
|
{
|
|
if ( CountInv(invoker.nextammo) <= 0 ) A_SwitchAmmoType(true);
|
|
int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1;
|
|
if ( invoker.nextammo && (CountInv(invoker.nextammo) > 0) )
|
|
flg |= WRF_ALLOWRELOAD;
|
|
else flg |= WRF_NOPRIMARY;
|
|
A_WeaponReady(flg);
|
|
if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK) )
|
|
invoker.CheckAmmo(EitherFire,true);
|
|
}
|
|
Wait;
|
|
Fire:
|
|
#### # 1
|
|
{
|
|
if ( invoker.fired ) return ResolveState("Reload");
|
|
A_FireShell();
|
|
return ResolveState(null);
|
|
}
|
|
XZW2 RSTU 1;
|
|
XZW2 VWXY 2;
|
|
Goto ReadyFired;
|
|
AltFire:
|
|
#### # 1 A_SwitchAmmoType();
|
|
#### # 1 A_AltHold();
|
|
Wait;
|
|
Reload:
|
|
#### # 1
|
|
{
|
|
A_PlayerReload();
|
|
A_SelectUnloadState();
|
|
}
|
|
Stop;
|
|
UnloadDummy: // overlay with shared functions for all unload anims
|
|
TNT1 A 11;
|
|
TNT1 A 14 A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP);
|
|
TNT1 A 1 A_DropShell();
|
|
Stop;
|
|
UnloadRedFired:
|
|
XZW2 Z 2;
|
|
XZW3 QRST 2;
|
|
XZW3 UVWXYZ 1;
|
|
XZW4 ABCDEFGH 1;
|
|
XZW8 M 1;
|
|
Goto Reload2;
|
|
UnloadGreenFired:
|
|
XZW2 Z 2;
|
|
XZW4 IJKL 2;
|
|
XZW4 MNOPQRSTUVWXYZ 1;
|
|
XZW9 T 1;
|
|
Goto Reload2;
|
|
UnloadWhiteFired:
|
|
XZW2 Z 2;
|
|
XZW5 ABCD 2;
|
|
XZW5 EFGHIJKLMNOPQR 1;
|
|
XZWB A 1;
|
|
Goto Reload2;
|
|
UnloadBlueFired:
|
|
XZW2 Z 2;
|
|
XZW5 STUV 2;
|
|
XZW5 WXYZ 1;
|
|
XZW6 ABCDEFGHIJ 1;
|
|
XZWC H 1;
|
|
Goto Reload2;
|
|
UnloadBlackFired:
|
|
XZW2 Z 2;
|
|
XZW6 KLMN 2;
|
|
XZW6 OPQRSTUVWXYZ 1;
|
|
XZW7 AB 1;
|
|
XZWD O 1;
|
|
Goto Reload2;
|
|
UnloadPurpleFired:
|
|
XZW2 Z 2;
|
|
XZW7 CDEF 2;
|
|
XZW7 GHIJKLMNOPQRST 1;
|
|
XZWE V 1;
|
|
Goto Reload2;
|
|
UnloadGoldFired:
|
|
XZW2 Z 2;
|
|
XZW7 UVWX 2;
|
|
XZW7 YZ 1;
|
|
XZW8 ABCDEFGHIJKL 1;
|
|
XZWG C 1;
|
|
Goto Reload2;
|
|
UnloadRed:
|
|
XZW2 A 2;
|
|
XZWK JKLM 2;
|
|
XZWK NOPQRSTUVWXYZ 1;
|
|
XZWL A 1;
|
|
XZWP F 1;
|
|
Goto Reload2;
|
|
UnloadGreen:
|
|
XZW2 A 2;
|
|
XZWL BCDE 2;
|
|
XZWL FGHIJKLMNOPQRS 1;
|
|
XZWQ M 1;
|
|
Goto Reload2;
|
|
UnloadWhite:
|
|
XZW2 A 2;
|
|
XZWL TUVW 2;
|
|
XZWL XYZ 1;
|
|
XZWM ABCDEFGHIJK 1;
|
|
XZWR T 1;
|
|
Goto Reload2;
|
|
UnloadBlue:
|
|
XZW2 A 2;
|
|
XZWM LMNO 2;
|
|
XZWM PQRSTUVWXYZ 1;
|
|
XZWN ABC 1;
|
|
XZWT A 1;
|
|
Goto Reload2;
|
|
UnloadBlack:
|
|
XZW2 A 2;
|
|
XZWN DEFG 2;
|
|
XZWN HIJKLMNOPQRSTU 1;
|
|
XZWU H 1;
|
|
Goto Reload2;
|
|
UnloadPurple:
|
|
XZW2 A 2;
|
|
XZWN VWXY 2;
|
|
XZWN Z 1;
|
|
XZWO ABCDEFGHIJKLM 1;
|
|
XZWV O 1;
|
|
Goto Reload2;
|
|
UnloadGold:
|
|
XZW2 A 2;
|
|
XZWO NOPQ 2;
|
|
XZWO RSTUVWXYZ 1;
|
|
XZWP ABCDE 1;
|
|
XZWW V 1;
|
|
Goto Reload2;
|
|
Reload2:
|
|
#### # 1 A_SelectLoadState();
|
|
Stop;
|
|
LoadDummy: // overlay with shared functions for all load anims
|
|
TNT1 A 9;
|
|
TNT1 A 12 A_LoadShell();
|
|
TNT1 A 2 A_StartSound("spreadgun/close",CHAN_WEAPON,CHANF_OVERLAP);
|
|
TNT1 A 2 A_Prime();
|
|
TNT1 A 1 { invoker.PlayUpSound(self); }
|
|
Stop;
|
|
LoadRedFired:
|
|
XZW8 MNOPQRSTUVWXYZ 1;
|
|
XZW9 ABCDEFGHIJKLMNOPQRS 1;
|
|
Goto Ready;
|
|
LoadGreenFired:
|
|
XZW9 TUVWXYZ 1;
|
|
XZWA ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
Goto Ready;
|
|
LoadWhiteFired:
|
|
XZWB ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWC ABCDEFG 1;
|
|
Goto Ready;
|
|
LoadBlueFired:
|
|
XZWC HIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWD ABCDEFGHIJKLMN 1;
|
|
Goto Ready;
|
|
LoadBlackFired:
|
|
XZWD OPQRSTUVWXYZ 1;
|
|
XZWE ABCDEFGHIJKLMNOPQRSTU 1;
|
|
Goto Ready;
|
|
LoadPurpleFired:
|
|
XZWE VWXYZ 1;
|
|
XZWF ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWG AB 1;
|
|
Goto Ready;
|
|
LoadGoldFired:
|
|
XZWG CDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWH ABCDEFGHI 1;
|
|
Goto Ready;
|
|
LoadRed:
|
|
XZWP FGHIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWQ ABCDEFGHIJKL 1;
|
|
Goto Ready;
|
|
LoadGreen:
|
|
XZWQ MNOPQRSTUVWXYZ 1;
|
|
XZWR ABCDEFGHIJKLMNOPQRS 1;
|
|
Goto Ready;
|
|
LoadWhite:
|
|
XZWR TUVWXYZ 1;
|
|
XZWS ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
Goto Ready;
|
|
LoadBlue:
|
|
XZWT ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWU ABCDEFG 1;
|
|
Goto Ready;
|
|
LoadBlack:
|
|
XZWU HIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWV ABCDEFGHIJKLMN 1;
|
|
Goto Ready;
|
|
LoadPurple:
|
|
XZWV OPQRSTUVWXYZ 1;
|
|
XZWW ABCDEFGHIJKLMNOPQRSTU 1;
|
|
Goto Ready;
|
|
LoadGold:
|
|
XZWW VWXYZ 1;
|
|
XZWX ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
|
|
XZWY AB 1;
|
|
Goto Ready;
|
|
Zoom:
|
|
XZW2 A 1
|
|
{
|
|
A_StartSound("spreadgun/checkgun",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_PlayerCheckGun();
|
|
return A_JumpIf(invoker.fired,"ZoomFired");
|
|
}
|
|
XZWH JKLMNOPQRST 1;
|
|
XZWH UVWXYZ 2;
|
|
XZWI ABC 2;
|
|
XZWI DEFGHI 1;
|
|
Goto Ready;
|
|
ZoomFired:
|
|
XZW2 Z 1;
|
|
XZWI WXYZ 1;
|
|
XZWJ ABCDEFG 1;
|
|
XZWJ HIJKLMNOP 2;
|
|
XZWJ QRSTUV 1;
|
|
Goto ReadyFired;
|
|
DummyMelee:
|
|
TNT1 A 3
|
|
{
|
|
A_Parry(9);
|
|
A_PlayerMelee(true);
|
|
}
|
|
TNT1 A 1 A_Melee();
|
|
Stop;
|
|
User1:
|
|
XZW2 A 2
|
|
{
|
|
A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP);
|
|
return A_JumpIf(invoker.fired,"User1Fired");
|
|
}
|
|
XZWI JK 2;
|
|
User1Hold:
|
|
XZWI L 1
|
|
{
|
|
A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_Overlay(-9999,"DummyMelee");
|
|
}
|
|
XZWI MNOP 2;
|
|
XZWI QR 3;
|
|
XZWI S 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1Hold");
|
|
XZWI S 0 { invoker.PlayUpSound(self); }
|
|
XZWI STUV 2;
|
|
Goto Ready;
|
|
User1Fired:
|
|
XZW2 Z 2;
|
|
XZWJ WX 2;
|
|
User1FiredHold:
|
|
XZWJ Y 1
|
|
{
|
|
A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_Overlay(-9999,"DummyMelee");
|
|
}
|
|
XZWJ Z 2;
|
|
XZWK ABC 2;
|
|
XZWK DE 3;
|
|
XZWK F 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1FiredHold");
|
|
XZWK F 0 { invoker.PlayUpSound(self); }
|
|
XZWK FGHI 2;
|
|
Goto ReadyFired;
|
|
FlashRed:
|
|
XZWZ A 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[3] = 120;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
FlashGreen:
|
|
XZWZ B 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[3] = 90;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
FlashWhite:
|
|
XZWZ C 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[1] = 176;
|
|
l.args[2] = 32;
|
|
l.args[3] = 160;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
FlashBlue:
|
|
XZWZ D 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[0] = 96;
|
|
l.args[1] = 224;
|
|
l.args[2] = 255;
|
|
l.args[3] = 160;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
FlashBlack:
|
|
XZWZ E 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[1] = 144;
|
|
l.args[2] = 16;
|
|
l.args[3] = 280;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
FlashPurple:
|
|
XZWZ F 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[3] = 60;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
FlashGold:
|
|
XZWZ G 2 Bright
|
|
{
|
|
let l = Spawn("SWWMWeaponLight",pos);
|
|
l.args[3] = 300;
|
|
l.target = self;
|
|
}
|
|
Stop;
|
|
}
|
|
}
|