swwmgz_m/zscript/weapons/swwm_shot.zsc

1187 lines
34 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 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 gibh = (Results.HitActor.GibHealth!=int.min)?-abs(Results.HitActor.GibHealth):-int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor);
// if gibbed, go through without dealing more damage
if ( Results.HitActor.health-amt <= gibh ) 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
double resist; // resistance against penetration loss (1.0 = don't lose)
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*(1.-resist)) >= penetration) || Results.HitActor.bNODAMAGE )
{
ent.hitdamage = int(penetration);
penetration = 0;
}
else
{
int gibh = (Results.HitActor.GibHealth!=int.min)?-abs(Results.HitActor.GibHealth):-int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor);
ent.hitdamage = min(Results.HitActor.health+abs(gibh),int(penetration));
penetration = max(0,penetration-(ent.hitdamage*(1.-resist)));
}
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 Spreadgun : SWWMWeapon
{
bool fired; // shell was used
transient bool wasfired; // for hammer priming
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
ui Class<Ammo> lastammo;
bool initialized;
override void HudTick()
{
Super.HudTick();
if ( lastammo && (lastammo != nextammo) && (Owner.player == players[consoleplayer]) )
{
let bar = SWWMStatusBar(statusbar);
if ( bar )
{
bar.ntagstr = GetDefaultByType(nextammo).GetTag();
bar.ntagtic = level.totaltime;
bar.ntagcol = nametagcolor;
}
}
lastammo = nextammo;
}
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 'BlueShell' ) return StringTable.Localize("$O_SPREADGUN_BLUE");
if ( loadammo is 'PurpleShell' ) return StringTable.Localize("$O_SPREADGUN_PURPLE");
if ( loadammo is 'BlackShell' ) return StringTable.Localize("$O_SPREADGUN_BLACK");
if ( loadammo is 'GoldShell' ) return StringTable.Localize("$O_SPREADGUN_GOLD");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override bool ReportHUDAmmo()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell","BlackShell","GoldShell"};
for ( int i=0; i<6; 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","BlueShell","PurpleShell","BlackShell","GoldShell"};
if ( (firemode == PrimaryFire) || (firemode == EitherFire) )
{
if ( !fired && chambered ) return true;
for ( int i=0; i<6; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
if ( autoswitch ) PlayerPawn(Owner).PickNewWeapon(null);
return false;
}
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
}
override bool UsesAmmo( Class<Ammo> kind )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell","BlackShell","GoldShell"};
for ( int i=0; i<6; i++ ) if ( kind is types[i] ) return true;
return false;
}
override bool IsCurrentAmmo( Class<Ammo> kind )
{
return (kind is nextammo);
}
action void A_SelectUnloadState()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell","BlackShell","GoldShell"};
static const statelabel primedstates[] = {"UnloadRed", "UnloadGreen", "UnloadBlue", "UnloadPurple", "UnloadBlack", "UnloadGold"};
static const statelabel firedstates[] = {"UnloadRedFired", "UnloadGreenFired", "UnloadBlueFired", "UnloadPurpleFired", "UnloadBlackFired", "UnloadGoldFired"};
int amidx = 0;
for ( int i=0; i<6; 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","BlueShell","PurpleShell","BlackShell","GoldShell"};
static const statelabel primedstates[] = {"LoadRed", "LoadGreen", "LoadBlue", "LoadPurple", "LoadBlack", "LoadGold"};
static const statelabel firedstates[] = {"LoadRedFired", "LoadGreenFired", "LoadBlueFired", "LoadPurpleFired", "LoadBlackFired", "LoadGoldFired"};
int amidx = 0;
for ( int i=0; i<6; i++ )
{
if ( invoker.nextammo != types[i] ) continue;
amidx = i;
break;
}
invoker.wasfired = false;
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
{
invoker.wasfired = true;
player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
}
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","BlueShell","PurpleShell","BlackShell","GoldShell"};
static const Class<Actor> casetypes[] = {"RedShellCasing","GreenShellCasing","BlueShellCasing","PurpleShellCasing","BlackShellCasing","GoldShellCasing"};
if ( !invoker.fired )
{
for ( int i=0; i<6; 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) )
invoker.BufferAmmo(types[i],1);
else amo.Amount++;
break;
}
}
else
{
for ( int i=0; i<6; 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;
let p = SWWMPuff.Setup(t.HitList[i].HitLocation,t.HitList[i].x,invoker,self,t.HitList[i].HitActor);
SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,p,self,'shot',false,DMG_INFLICTOR_IS_PUFF);
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","BlueShell","PurpleShell","BlackShell","GoldShell"};
static const statelabel flashes[] = {"FlashRed","FlashGreen","FlashBlue","FlashPurple","FlashBlack","FlashGold"};
static const String sounds[] = {"spreadgun/redfire","spreadgun/greenfire","spreadgun/bluefire","spreadgun/purplefire","spreadgun/blackfire","spreadgun/goldfire"};
static const int louds[] = {800,1000,1200,600,1400,2500};
static const int quakes[] = {3,4,4,1,3,6};
static const Color cols[] = {Color(40,255,192,64),Color(36,255,192,80),Color(48,32,176,255),Color(24,255,224,96),Color(72,255,128,16),Color(96,255,224,16)};
for ( int i=0; i<6; 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|QF_3D,rollIntensity:.2*quakes[i]);
A_BumpFOV(1.-quakes[i]*.04);
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 = 200.;
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.01);
dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
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<8; j++ )
{
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.3);
let b = Spawn("SaltBeam",level.Vec3Offset(origin,SWWMUtility.CircleOffset(y,z,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 3:
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.03);
dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
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 4:
for ( int j=0; j<50; j++ )
{
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.1);
dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
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,.01);
dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
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.);
SWWMUtility.AchievementProgressInc("golden",1,player);
break;
default:
st = new("SpreadgunTracer");
st.ignoreme = self;
for ( int j=0; j<20; j++ )
{
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.12);
dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
st.hitlist.Clear();
st.shootthroughlist.Clear();
st.waterhitlist.Clear();
st.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
ProcessTraceHit(st,origin,dir,8,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);
if ( !invoker.FetchBufferedAmmo(invoker.nextammo,1) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
{
let amo = FindInventory(invoker.nextammo);
if ( amo && (amo.Amount > 0) ) amo.Amount--;
}
invoker.chambered = true;
invoker.fired = false;
invoker.loadammo = invoker.nextammo;
invoker.ClearBufferedAmmo();
}
action void A_Prime()
{
if ( invoker.fired || invoker.wasfired )
A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP);
}
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","BlueShell","PurpleShell","BlackShell","GoldShell"};
if ( !initialized )
{
initialized = true;
if ( !loadammo ) loadammo = "RedShell";
fired = false;
chambered = true;
}
for ( int i=0; i<6; i++ )
{
Ammo a = Ammo(other.FindInventory(types[i]));
if ( !a ) continue;
nextammo = types[i];
Super.AttachToOwner(other);
return;
}
nextammo = AmmoType1;
Super.AttachToOwner(other);
}
action void A_SwitchAmmoType( bool rev = false )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell","BlackShell","GoldShell"};
int cur = 0, next = 0;
for ( int i=0; i<6; 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<5; i++ )
{
if ( CountInv(types[i]) <= 0 ) continue;
ridx = i;
break;
}
}
if ( ridx != -1 ) next = ridx;
}
else
{
for ( int i=0; i<6; i++ )
{
ridx = (i+cur+1)%6;
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|WRF_NOSWITCH);
}
action void A_AltHold()
{
A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH);
// 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));
}
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkSound("spreadgun/open");
MarkSound("spreadgun/hammer");
MarkSound("spreadgun/close");
MarkSound("spreadgun/shellin");
MarkSound("spreadgun/select");
MarkSound("spreadgun/deselect");
MarkSound("spreadgun/redfire1");
MarkSound("spreadgun/redfire2");
MarkSound("spreadgun/greenfire1");
MarkSound("spreadgun/greenfire2");
MarkSound("spreadgun/bluefire1");
MarkSound("spreadgun/bluefire2");
MarkSound("spreadgun/blackfire1");
MarkSound("spreadgun/blackfire2");
MarkSound("spreadgun/purplefire1");
MarkSound("spreadgun/purplefire2");
MarkSound("spreadgun/goldfire1");
MarkSound("spreadgun/goldfire2");
MarkSound("spreadgun/checkgun");
MarkSound("spreadgun/casing1");
MarkSound("spreadgun/casing2");
MarkSound("spreadgun/casing3");
MarkSound("spreadgun/casing4");
MarkSound("spreadgun/casing5");
MarkSound("spreadgun/casing6");
MarkSound("spreadgun/gcasing1");
MarkSound("spreadgun/gcasing2");
MarkSound("spreadgun/gcasing3");
MarkSound("spreadgun/pellet1");
MarkSound("spreadgun/pellet2");
MarkSound("spreadgun/pellet3");
MarkSound("spreadgun/pellet4");
MarkSound("spreadgun/pellet5");
MarkSound("spreadgun/pellet6");
MarkSound("spreadgun/pellet7");
MarkSound("spreadgun/pellet8");
MarkSound("spreadgun/pelletf1");
MarkSound("spreadgun/pelletf2");
MarkSound("spreadgun/pelletf3");
MarkSound("spreadgun/pelletf4");
MarkSound("spreadgun/pelletf5");
MarkSound("spreadgun/pelletf6");
MarkSound("spreadgun/slug1");
MarkSound("spreadgun/slug2");
MarkSound("spreadgun/slugf1");
MarkSound("spreadgun/slugf2");
MarkSound("spreadgun/corrode");
MarkSound("spreadgun/corrodepuff1");
MarkSound("spreadgun/corrodepuff2");
MarkSound("spreadgun/ball1");
MarkSound("spreadgun/ball2");
MarkSound("spreadgun/ball3");
MarkSound("spreadgun/ballf1");
MarkSound("spreadgun/ballf2");
MarkSound("spreadgun/ballf3");
MarkSound("spreadgun/salt1");
MarkSound("spreadgun/salt2");
MarkSound("spreadgun/salt3");
MarkSound("spreadgun/salt4");
MarkSound("spreadgun/salttrail1");
MarkSound("spreadgun/salttrail2");
MarkSound("spreadgun/salttrail3");
MarkSound("spreadgun/salttrail4");
MarkSound("spreadgun/goldexpl1");
MarkSound("spreadgun/goldexpl2");
MarkSound("misc/clonk1");
MarkSound("misc/clonk2");
MarkSound("misc/clonk3");
MarkSound("misc/clonk4");
MarkSound("misc/clonk5");
MarkSound("misc/clonk6");
MarkSound("misc/clonk7");
MarkSound("misc/clonk8");
MarkSound("misc/clonk9");
MarkSound("misc/clonk10");
MarkSound("misc/clonk11");
}
Default
{
Tag "$T_SPREADGUN";
Inventory.PickupMessage "$I_SPREADGUN";
Obituary "$O_SPREADGUN";
SWWMWeapon.Tooltip "$TT_SPREADGUN";
SWWMWeapon.GetLine "getspreadgun";
Weapon.UpSound "spreadgun/select";
Weapon.SlotNumber 3;
Weapon.SelectionOrder 500;
Weapon.AmmoType1 "RedShell";
Weapon.AmmoGive1 1;
SWWMWeapon.DropAmmoType "SWWMShellAmmoSmall";
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;
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;
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;
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;
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;
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;
}
}