swwmgz_m/zscript/swwm_shot.zsc

2935 lines
81 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 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;
if ( (Results.HitActor.Health >= int(penetration)) || Results.HitActor.bNODAMAGE )
{
ent.hitdamage = int(penetration);
penetration = 0;
}
else
{
ent.hitdamage = min(Results.HitActor.health+int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor),int(penetration));
penetration = max(0,penetration-ent.hitdamage);
}
hitlist.Push(ent);
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;
+NOTELEPORT;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:200);
A_StartSound("spreadgun/pellet",CHAN_VOICE,CHANF_DEFAULT,.4,4.);
A_SprayDecal("TinyPock",-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;
+NOTELEPORT;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:500);
A_StartSound("spreadgun/slug",CHAN_VOICE,CHANF_DEFAULT,1.,2.);
A_SprayDecal("Pock",-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.,32.);
let l = Spawn("PaletteLight",pos);
l.Args[3] = int(60+60*(ReactionTime/20.));
l.ReactionTime = ReactionTime+2;
l.target = self;
}
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<BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = BlockingFloor.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.top.Normal;
else HitNormal = BlockingFloor.floorplane.Normal;
}
else if ( BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = BlockingCeiling.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.bottom.Normal;
else HitNormal = BlockingCeiling.ceilingplane.Normal;
}
else if ( BlockingLine )
{
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
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,.6))+vel);
bHITOWNER = true;
}
States
{
Spawn:
TNT1 A 1
{
if ( waterlevel > 0 ) ReactionTime -= 2;
let p = Spawn("DragonBreathPuff",pos);
p.alpha *= .6+.4*(ReactionTime/20.);
p.scale *= 3.5-2.5*(ReactionTime/20.);
SWWMUtility.DoExplosion(self,4+(reactiontime/2),1000+200*reactiontime,120+5*reactiontime,flags:DE_HOWL,ignoreme:bHITOWNER?null:target);
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;
}
}
A_CountDown();
}
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 "SaltExpl,1";
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 "Electric";
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(swwm_uncapalert?0:6000);
SWWMUtility.DoExplosion(self,25+special2*4,15000,100,40);
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);
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
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 "Electric";
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);
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] == 2) || swwm_omnibust ) BusterWall.Bust(t.Results,60+Accuracy*10,target,x,t.Results.HitPos.z);
if ( t.Results.HitType == TRACE_HitActor )
{
SWWMUtility.DoKnockback(t.Results.HitActor,x,25000);
t.Results.HitActor.DamageMobj(self,target,35+Accuracy*5,'Salt',DMG_THRUSTLESS);
if ( t.Results.HitActor && t.Results.HitActor.bIsMonster && !Random[Spreadgun](0,3) )
t.Results.HitActor.Howl();
}
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) )
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 )
SWWMUtility.DoExplosion(self,5+Accuracy,5000,32,flags:DE_HOWL,ignoreme:target);
if ( ((special2%4) || args[2]) && !special1 ) SpreadOut();
args[2]++;
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 BallImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0: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 CorrodeSmoke : SWWMHalfSmoke
{
Default
{
RenderStyle "Add";
Alpha 0.25;
}
States
{
Spawn:
FRT1 ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 Bright A_SetTics(1+special1);
FRT2 ABCDEFGHI 1 Bright A_SetTics(1+special1);
Stop;
}
}
Class CorrodeDebuff : Inventory
{
int cnt, cnt2, cnt3;
Actor instigator;
bool wasalive;
Default
{
Obituary "$O_SPREADGUN_BLACK_DEBUFF";
Inventory.Amount 1;
Inventory.MaxAmount 1000;
Inventory.InterHubAmount 0;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
}
override int DoSpecialDamage( Actor target, int Damage, Name DamageType )
{
if ( target == Owner ) return Damage;
// spread ourselves
let c = CorrodeDebuff(target.FindInventory("CorrodeDebuff"));
if ( !c )
{
c = CorrodeDebuff(Spawn("CorrodeDebuff",target.pos));
c.AttachToOwner(target);
c.cnt = 5; // slight delay
c.Amount = min(Damage*8,int(Amount*.6)); // prevent "escalating" spread
c.A_StartSound("spreadgun/corrode",CHAN_VOICE,CHANF_DEFAULT,min(c.Amount/100.,1.));
}
else if ( c.Amount < int(Amount*.6) ) c.Amount = min(c.Amount+Damage*8,int(Amount*.6)); // prevent "escalating" spread
c.instigator = instigator;
return 0; // no direct damage
}
override void AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
cnt3 = Random[Corrode](0,19); // randomize puff sound offset
}
override void OwnerDied()
{
Super.OwnerDied();
// forcibly update
wasalive = true;
cnt = -1;
}
override void DoEffect()
{
Super.DoEffect();
if ( (Amount <= 0) || !Owner )
{
if ( Owner && IsActorPlayingSound(CHAN_VOICE) )
{
// don't destroy yet, keep following owner until the sound wears off
SetOrigin(Owner.Vec3Offset(0,0,Owner.height/2),false);
return;
}
DepleteOrDestroy();
return;
}
SetOrigin(Owner.Vec3Offset(0,0,Owner.height/2),false);
cnt--;
if ( cnt < 0 )
{
if ( !Random[Corrode](0,3) && (Owner.Health > 0) ) Owner.Howl();
double maxrad = max(Owner.radius,Owner.height);
int defh = Owner.GetSpawnHealth();
int flg = DMG_THRUSTLESS;
if ( Owner is 'Centaur' ) flg |= DMG_FOILINVUL; // you're melting, that shield is worthless
Owner.DamageMobj(self,instigator?instigator:Actor(self),clamp(Amount/8,1,50),'Corroded',flg);
bool justdied = (wasalive && (!Owner || (Owner.Health <= 0)));
if ( justdied )
{
maxrad += 60;
Amount = min(Amount+int(defh**.5),MaxAmount);
A_StartSound("spreadgun/corrode",CHAN_VOICE,CHANF_DEFAULT);
}
if ( !wasalive ) maxrad += 25;
SWWMUtility.DoExplosion(self,clamp(Amount/8,1,50),0,maxrad*1.2,maxrad*.9,DE_NOBLEED|DE_NOSPLASH|DE_HOWL,'Corroded',Owner);
if ( !Owner ) return; // yeah this can happen
int smokefact = int(clamp(maxrad/32.,1,8));
int numpt = Random[Corrode](0,2*smokefact);
if ( justdied ) numpt = Random[Corrode](8,12)*smokefact;
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("CorrodeSmoke",level.Vec3Offset(Owner.pos,(.8*FRandom[Corrode](-Owner.radius,Owner.radius),.8*FRandom[Corrode](-Owner.radius,Owner.radius),FRandom[Corrode](.1*Owner.Height,.9*Owner.Height))));
s.vel = Owner.vel*.5;
if ( justdied ) s.vel += (FRandom[Corrode](-1,1),FRandom[Corrode](-1,1),FRandom[Corrode](-1,1))*FRandom[Corrode](2.,8.);
else if ( !wasalive ) s.vel += (FRandom[Corrode](-1,1),FRandom[Corrode](-1,1),FRandom[Corrode](-1,1))*FRandom[Corrode](.2,2.);
else s.vel += (FRandom[Corrode](-1,1),FRandom[Corrode](-1,1),FRandom[Corrode](-1,1))*FRandom[Corrode](.1,1.);
s.scale *= FRandom[Corrode](2.,3.);
s.alpha *= Clamp(Amount/10.,0.,.6);
if ( justdied ) s.special1 = Random[Corrode](0,2);
else if ( !wasalive ) s.special1 = Random[Corrode](0,1);
}
cnt = clamp((100-Amount)/10,5,15);
}
wasalive = (Owner && Owner.Health > 0);
cnt3++;
if ( !(cnt3%20) ) A_StartSound("spreadgun/corrodepuff",CHAN_BODY,CHANF_OVERLAP,min(Amount/200.,1.),2.,FRandom[Corrode](.8,1.2)-min(Amount/200.,.4));
cnt2++;
if ( cnt2 < 3 ) return;
cnt2 = (!Owner||(Owner.Health<=Owner.GetGibHealth()))?-2:0; // slower decay when gibbed
Amount -= 1;
if ( Amount > 100 ) Amount -= max(0,Amount/50-1);
if ( !Owner ) return;
if ( Owner.Health > 0 ) Amount -= 1;
if ( Owner.bNOBLOOD ) Amount -= 2;
}
}
Class FlechetteTracer : 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_BLOCKEVERYTHING|Line.ML_BLOCKPROJECTILE)) )
return TRACE_Stop;
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
return TRACE_Stop;
}
}
Class CorrosiveSplash : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+NOTELEPORT;
+NOINTERACTION;
}
override int DoSpecialDamage( Actor target, int Damage, Name DamageType )
{
// spread ourselves
let c = CorrodeDebuff(target.FindInventory("CorrodeDebuff"));
if ( !c )
{
c = CorrodeDebuff(Spawn("CorrodeDebuff",target.pos));
c.AttachToOwner(target);
c.cnt = 5; // slight delay
}
c.Amount = min(c.Amount+Damage,c.MaxAmount);
c.A_StartSound("spreadgun/corrode",CHAN_VOICE,CHANF_DEFAULT,min(c.Amount/100.,1.));
c.instigator = self.target;
return 0; // no direct damage
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SWWMUtility.DoExplosion(self,20,0,50,15,DE_NOBLEED|DE_NOSPLASH|DE_HOWL,'Corroded',tracer);
Destroy();
}
}
Class CorrosiveFlechette : Actor
{
transient FlechetteTracer t;
Actor lasthit;
Default
{
Obituary "$O_SPREADGUN_BLACK";
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
+NODAMAGETHRUST;
+NOINTERACTION;
+INTERPOLATEANGLES;
Speed 100;
Radius .1;
Height 0.;
Gravity .35;
}
override void Tick()
{
prev = pos; // interpolation
if ( isFrozen() ) return;
if ( CurState == SpawnState )
{
// bullet trace
if ( (pos.z < floorz) || (pos.z > ceilingz) )
{
// the fuck just happened???
SetStateLabel("Death");
return;
}
Vector3 dir = vel;
double dist = vel.length();
if ( dist < 1. )
{
// somehow have no velocity while alive, just die
SetStateLabel("Death");
return;
}
dir /= dist;
if ( !t ) t = new("FlechetteTracer");
if ( lasthit ) t.ignore = lasthit;
else if ( !bHITOWNER ) t.ignore = target;
else t.ignore = null;
t.shootthroughlist.Clear();
t.waterhitlist.Clear();
t.Trace(pos,CurSector,dir,dist,TRACE_HitSky);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Spawn("SmolInvisibleSplasher",t.WaterHitList[i].hitpos);
b.A_CheckTerrain();
}
for ( int i=10; i<t.Results.Distance; i+=20 )
{
if ( Random[Boolet](0,2) ) continue;
let b = Actor.Spawn("SWWMHalfSmoke",level.Vec3Offset(pos,dir*i));
b.Scale *= FRandom[Boolet](.3,.5);
b.A_SetRenderStyle(b.alpha*.2,STYLE_AddShaded);
b.SetShade(Color(3,4,3)*Random[Boolet](10,20));
b.vel += vel*.05;
}
if ( t.Results.HitType == TRACE_HitNone )
{
// advance bullet
SetOrigin(level.Vec3Offset(pos,dir*dist),true);
vel = t.Results.HitVector*dist-(0,0,GetGravity()); // slight drop
dir = vel.unit();
angle = atan2(dir.y,dir.x);
pitch = asin(-dir.z);
return;
}
else if ( t.Results.HitType == TRACE_HitActor )
{
Vector3 hitnormal = -t.Results.HitVector;
let a = t.Results.HitActor;
if ( a.bSHOOTABLE && (a.Health > 0) )
{
let c = CorrodeDebuff(a.FindInventory("CorrodeDebuff"));
if ( !c )
{
c = CorrodeDebuff(Spawn("CorrodeDebuff",a.pos));
c.AttachToOwner(a);
}
c.Amount = min(c.Amount+25,c.MaxAmount);
c.cnt = 0;
c.instigator = target;
c.A_StartSound("spreadgun/corrode",CHAN_VOICE,CHANF_DEFAULT,min(c.Amount/100.,1.));
}
int dmg = 3;
int amt = 3;
if ( a.bSHOOTABLE )
{
SWWMUtility.DoKnockback(a,t.Results.HitVector,1000);
SWWMDamageAccumulator.Accumulate(a,dmg,self,target,'Shot');
amt = SWWMDamageAccumulator.GetAmount(a);
}
if ( !a.bSHOOTABLE || a.bNOBLOOD || a.bDORMANT || a.bINVULNERABLE )
{
let p = Spawn("SpreadImpact",t.Results.HitPos);
A_SetAngle(atan2(hitnormal.y,hitnormal.x),SPF_INTERPOLATE);
A_SetPitch(asin(-hitnormal.z),SPF_INTERPOLATE);
}
else
{
a.TraceBleed(dmg,self);
a.SpawnBlood(t.Results.HitPos,atan2(t.Results.HitVector.y,t.Results.HitVector.x)+180,dmg);
A_StartSound("spreadgun/pelletf",CHAN_VOICE,CHANF_DEFAULT,.5,2.);
}
let s = Spawn("CorrosiveSplash",t.Results.HitPos);
s.target = target;
s.tracer = a;
// chance to pierce
int posthealth = a.health-amt;
double hratio = posthealth/double(a.GetSpawnHealth());
if ( (!a.bSHOOTABLE && !Random[Corrode](0,2)) || (posthealth <= 0) || (FRandom[Corrode](hratio,1.) < .5) )
{
SetOrigin(t.Results.HitPos,true);
dir = t.Results.HitVector;
A_SetAngle(atan2(dir.y,dir.x),SPF_INTERPOLATE);
A_SetPitch(asin(-dir.z),SPF_INTERPOLATE);
vel = dir*dist;
bHITOWNER = true;
lasthit = t.Results.HitActor;
}
else SetStateLabel("Death");
return;
}
else
{
// Wall busting
if ( swwm_omnibust )
BusterWall.Bust(t.Results,3,target,t.Results.HitVector,t.Results.HitPos.z);
// check what we hit
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;
t.Results.HitLine.RemoteActivate(target,t.Results.Side,SPAC_Impact,t.Results.HitPos);
}
if ( t.Results.HitType != TRACE_HasHitSky )
{
let p = Spawn("SpreadImpact",t.Results.HitPos+hitnormal);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
if ( t.Results.HitType == TRACE_HitFloor ) p.CheckSplash(40);
let s = Spawn("CorrosiveSplash",t.Results.HitPos+hitnormal);
s.target = target;
}
// can we bounce?
if ( t.Results.HitVector dot hitnormal > -.35 )
{
SetOrigin(t.Results.HitPos+hitnormal,true);
dir = t.Results.HitVector-2*hitnormal*(t.Results.HitVector dot hitnormal);
A_SetAngle(atan2(dir.y,dir.x),SPF_INTERPOLATE);
A_SetPitch(asin(-dir.z),SPF_INTERPOLATE);
vel = dir*dist;
bHITOWNER = true;
lasthit = null;
}
else SetStateLabel("Death");
return;
}
}
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XZW1 A -1;
Stop;
Death:
TNT1 A 35;
Stop;
}
}
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.+heat*40);
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(swwm_uncapalert?0:2500);
A_StartSound(snd,CHAN_VOICE,CHANF_OVERLAP,1.,.2);
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;
}
SWWMUtility.DoKnockback(victim,vel.unit(),slamforce);
bool bleeds = (victim && !victim.bINVULNERABLE && !victim.bNOBLOOD && !victim.bDORMANT && is_schutt);
int newdmg = 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((newdmg>0)?newdmg: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 )
SWWMUtility.DoExplosion(self,dmg/2,25000,150,80,ignoreme:target);
// 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_LOOP,.6,3.,2.);
heat = 1.;
}
override void Tick()
{
oldvel = vel;
Super.Tick();
if ( isFrozen() ) return;
if ( InStateSequence(CurState,ResolveState("Death")) )
{
deadtimer++;
if ( deadtimer > 300 )
{
let numpt = Random[Spreadgun](3,6);
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("SWWMSmallSmoke",pos);
s.scale *= 3.;
s.alpha *= .2;
s.vel = pvel;
}
Destroy();
}
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;
if ( BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = BlockingFloor.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.top.Normal;
else HitNormal = BlockingFloor.floorplane.Normal;
}
else if ( BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = BlockingCeiling.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.bottom.Normal;
else HitNormal = BlockingCeiling.ceilingplane.Normal;
}
else if ( BlockingLine )
{
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
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);
}
}
else if ( BlockingMobj )
{
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
HitNormal = diff.unit();
}
// send the needed data for a bust
if ( (special1 == 2) || swwm_omnibust )
{
int dmg = int(oldvel.length()*4.2+heat*80);
BusterWall.ProjectileBust(self,dmg,oldvel.unit());
}
// 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 && !BlockingMobj.bDORMANT )
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 GExploRing : Actor
{
Default
{
RenderStyle "Add";
Scale 8.;
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+NOINTERACTION;
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XRG0 ABCDEFGHIJKLMNOPQRSTUVWX 1 Bright A_SetScale(scale.x*1.05);
Stop;
}
}
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(swwm_uncapalert?0:40000);
SWWMUtility.DoExplosion(self,777,90000,600,500,DE_EXTRAZTHRUST);
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("WumboRocketBlast",-172);
Scale *= FRandom[ExploS](0.8,1.1);
Scale.x *= RandomPick[ExploS](-1,1);
Scale.y *= RandomPick[ExploS](-1,1);
int numpt = Random[ExploS](30,40);
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,6);
s.scale *= 3.5;
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);
let r = Spawn("GExploRing",pos);
}
action void A_GoldSpread()
{
special1++;
if ( (special1%4) || (special1 > 30) ) return;
SWWMUtility.DoExplosion(self,777,90000,600-special1*15,500-special1*15,DE_EXTRAZTHRUST);
FLineTraceData d;
Vector3 HitNormal;
Vector3 origin;
double ang, pt;
for ( int i=0; i<6; i++ )
{
double totaldist = 30+(special1**2.)*.4;
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-(1.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;
}
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XEX1 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ[[\\]] 1 Bright A_GoldSpread();
Stop;
}
}
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();
SWWMUtility.DoExplosion(self,77,80000,500,400,DE_EXTRAZTHRUST);
A_QuakeEx(7,7,7,20,0,2000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollintensity:.8);
A_SprayDecal("BigRocketBlast",-172);
Scale *= FRandom[ExploS](0.8,1.1);
Scale.x *= RandomPick[ExploS](-1,1);
Scale.y *= RandomPick[ExploS](-1,1);
int numpt = Random[ExploS](6,10);
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,4);
s.scale *= 2.6;
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 > 20) ) return;
SWWMUtility.DoExplosion(self,77,80000,500-special1*10,400-special1*10,DE_EXTRAZTHRUST);
FLineTraceData d;
Vector3 HitNormal;
Vector3 origin;
double ang, pt;
for ( int i=0; i<3; i++ )
{
double totaldist = 20+(special1**2.)*.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-(1.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;
}
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XEX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright A_GoldSubSpread();
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();
SWWMUtility.DoExplosion(self,7,70000,400,300,DE_EXTRAZTHRUST);
A_QuakeEx(4,4,4,15,0,1000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollintensity:.4);
A_SprayDecal("RocketBlast",-172);
Scale *= FRandom[ExploS](0.8,1.1);
Scale.x *= RandomPick[ExploS](-1,1);
Scale.y *= RandomPick[ExploS](-1,1);
int numpt = Random[ExploS](2,4);
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,3);
s.scale *= 1.6;
s.alpha *= .2;
}
}
action void A_GoldSubSubSpread()
{
special1++;
if ( (special1%2) || (special1 > 10) ) return;
SWWMUtility.DoExplosion(self,7,70000,400-special1*5,300-special1*5,DE_EXTRAZTHRUST);
}
override void Tick()
{
if ( isFrozen() ) return;
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XEX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright A_GoldSubSubSpread();
Stop;
}
}
Class Spreadgun : SWWMWeapon
{
bool fired; // shell was used
bool chambered; // a shell is actually loaded
bool emptyup; // next reload will keep the chamber empty
Class<Ammo> loadammo, nextammo; // currently loaded shell, next shell to load
bool initialized;
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;
}
}
if ( !chambered ) return;
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 && chambered);
}
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 && chambered ) 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.chambered ) player.SetPSprite(PSP_WEAPON,invoker.FindState("UnloadEmpty")); // no "fired" one for this, as it can never happen
else if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx]));
else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
if ( invoker.chambered ) A_Overlay(-9999,"UnloadDummy");
else A_Overlay(-9999,"UnloadDummyEmpty");
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.emptyup )
{
if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState("LoadEmpty"));
else player.SetPSprite(PSP_WEAPON,invoker.FindState("LoadEmptyFired"));
}
else if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx]));
else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
if ( !invoker.emptyup && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
{
let amo = FindInventory(invoker.nextammo);
if ( amo && (amo.Amount > 0) ) amo.Amount--;
}
if ( invoker.emptyup ) A_Overlay(-9999,"LoadDummyEmpty");
else A_Overlay(-9999,"LoadDummy");
invoker.emptyup = false;
}
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) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
amo.CreateTossable(1);
amo.Amount++;
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 )
{
if ( swwm_omnibust )
{
// Wall busting
int bustdmg = dmg;
if ( t is 'SpreadSlugTracer' ) bustdmg = int(SpreadSlugTracer(t).penetration);
BusterWall.Bust(t.Results,bustdmg,self,t.Results.HitVector,t.Results.HitPos.z);
}
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(self,0,SPAC_PCross);
t.ShootThroughList[i].Activate(self,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&&!swwm_shotgib);
SWWMUtility.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.bDORMANT || 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.HitType == TRACE_HitFloor ) p.CheckSplash(40);
if ( t.Results.HitLine ) t.Results.HitLine.RemoteActivate(self,t.Results.Side,SPAC_Impact,t.Results.HitPos);
}
}
override Vector3 GetTraceOffset()
{
return (10.,2.,-2.);
}
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(swwm_uncapalert?0: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 = 150.;
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<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);
}
SWWMUtility.DoKnockback(self,-x,25000.);
break;
case 2:
for ( int j=0; j<3; j++ )
{
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.24);
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<15; 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<12; 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);
}
SWWMUtility.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<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);
}
SWWMUtility.DoKnockback(self,-x,23000.);
break;
case 4:
for ( int j=0; j<50; 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("CorrosiveFlechette",origin);
p.target = self;
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = dir*p.speed*FRandom[Spreadgun](1.,1.5);
}
for ( int i=0; i<10; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .7;
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);
}
SWWMUtility.DoKnockback(self,-x,15000.);
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<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);
}
SWWMUtility.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<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);
}
SWWMUtility.DoKnockback(self,-x,30000.);
break;
default:
st = new("SpreadgunTracer");
st.ignoreme = self;
for ( int j=0; j<20; 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<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);
}
SWWMUtility.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.chambered = true;
invoker.loadammo = invoker.nextammo;
}
action void A_Prime()
{
if ( invoker.fired )
{
A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP);
invoker.fired = false;
}
}
override bool PickupForAmmoSWWM( SWWMWeapon ownedWeapon )
{
bool good = Super.PickupForAmmoSWWM(ownedWeapon);
let Owner = ownedWeapon.Owner;
if ( (AmmoGive1 == 0) && loadammo && !fired && chambered )
{
let cur = Owner.FindInventory(loadammo);
if ( !cur )
{
cur = Inventory(Spawn(loadammo));
cur.Amount = 0;
cur.AttachToOwner(Owner);
}
// give the loaded shell (or drop)
if ( cur.Amount >= cur.MaxAmount ) cur.CreateTossable(1);
cur.Amount++;
good = true;
}
return good;
}
override void AttachToOwner( Actor other )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
Super.AttachToOwner(other);
if ( !initialized )
{
initialized = true;
if ( !loadammo ) loadammo = "RedShell";
fired = false;
chambered = true;
}
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);
// tap fire to unload round
if ( invoker.chambered && (player.cmd.buttons&BT_ATTACK) )
{
invoker.emptyup = true;
player.SetPSPrite(PSP_WEAPON,invoker.FindState("Reload"));
return;
}
if ( player.cmd.buttons&BT_ALTATTACK ) return;
A_SwitchAmmoType();
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 500;
Weapon.AmmoType1 "RedShell";
Weapon.AmmoGive1 1;
SWWMWeapon.DropAmmoType "Shell";
Stamina 15000;
+SWWMWEAPON.NOFIRSTGIVE;
Radius 10;
Height 24;
}
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) || !invoker.chambered) )
flg |= WRF_ALLOWRELOAD;
A_WeaponReady(flg);
return ResolveState(null);
}
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);
return ResolveState(null);
}
Wait;
Fire:
#### # 1
{
if ( invoker.fired || !invoker.chambered ) return ResolveState("Reload");
A_FireShell();
return ResolveState(null);
}
XZW2 RSTU 1;
XZW2 VWXY 2;
Goto ReadyFired;
AltFire:
#### # 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
{
invoker.chambered = false;
A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP);
}
TNT1 A 1 A_DropShell();
Stop;
UnloadDummyEmpty:
TNT1 A 11;
TNT1 A 14 A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP);
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;
UnloadEmpty:
XZW2 A 2;
XZWY CDEF 2;
XZWY GHIJKLMNOPQRSTU 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;
LoadDummyEmpty:
TNT1 A 9;
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;
LoadEmpty:
XZWY UVWXYZ 1;
XZWZ ABCDEFGHIJKLMNO 1;
Goto Ready;
LoadEmptyFired:
XZWZ PQRSTUVWXYZ 1;
XZW0 HIJKLMNOPQ 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:
XZW0 A 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 120;
l.target = self;
}
Stop;
FlashGreen:
XZW0 B 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 90;
l.target = self;
}
Stop;
FlashWhite:
XZW0 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:
XZW0 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:
XZW0 E 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 60;
l.target = self;
}
Stop;
FlashPurple:
XZW0 F 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 60;
l.target = self;
}
Stop;
FlashGold:
XZW0 G 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 300;
l.target = self;
}
Stop;
}
}