swwmgz_m/zscript/swwm_shot.zsc
Marisa Kirisame 7a01fdc4e8 Spreadgun buckshot and slug implemented.
Tweaks here and there to other stuff.
Refined wall jumping/climbing, also works on actors (including other players).
Refined how boosting/dashing handles falling speeds.
Added improved air control. It was very much needed.
Added "kick" sounds to wall jumps.
Add option to hear other player's voices in mp.
Fix some broken localization.
Fix invulnerable monsters bleeding from some attacks.
Fix desync when jumping on top of another player with prediction enabled.
Make moths immune to your damage, so you can stop accidentally killing them.
Make normal ammo buyable in Hexen again.
2020-02-27 02:00:17 +01:00

1086 lines
30 KiB
Text

// Blackmann "Rhino Stopper" Spreadgun (from Instant Action 3, also planned for Zanaveth Ultra Suite 2)
// Slot 3, replaces Shotgun, Ethereal Crossbow, Serpent Staff
Class RedShellCasing : SWWMCasing
{
Default
{
BounceSound "spreadgun/casing";
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
heat = 0;
}
}
Class GreenShellCasing : RedShellCasing {}
Class WhiteShellCasing : RedShellCasing {}
Class BlueShellCasing : RedShellCasing {}
Class BlackShellCasing : RedShellCasing {}
Class PurpleShellCasing : RedShellCasing {}
Class GoldShellCasing : RedShellCasing
{
Default
{
BounceSound "spreadgun/gcasing";
}
}
Class SWWMDamageAccumulator : Thinker
{
Actor victim, inflictor, source;
Array<Int> amounts;
int total;
Name type;
bool dontgib;
override void Tick()
{
Super.Tick();
// so many damn safeguards in this
if ( !victim )
{
Destroy();
return;
}
int gibhealth = victim.GetGibHealth();
// おまえはもう死んでいる
if ( (victim.health-total <= gibhealth) && !dontgib )
{
// safeguard for inflictors that have somehow ceased to exist, which apparently STILL CAN HAPPEN
if ( inflictor ) inflictor.bEXTREMEDEATH = true;
else type = 'Extreme';
}
// make sure accumulation isn't reentrant
// 何?
for ( int i=0; i<amounts.Size(); i++ )
{
if ( !victim ) break;
victim.DamageMobj(inflictor,source,amounts[i],type,DMG_THRUSTLESS);
}
// clean up
if ( inflictor ) inflictor.bEXTREMEDEATH = false;
Destroy();
}
static void Accumulate( Actor victim, int amount, Actor inflictor, Actor source, Name type, bool dontgib = false )
{
if ( !victim ) return;
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
SWWMDamageAccumulator a, match = null;
while ( a = SWWMDamageAccumulator(ti.Next()) )
{
if ( a.victim != victim ) continue;
match = a;
break;
}
if ( !match )
{
match = new("SWWMDamageAccumulator");
match.ChangeStatNum(STAT_USER);
match.victim = victim;
match.amounts.Clear();
}
match.amounts.Push(amount);
match.total += amount;
match.inflictor = inflictor;
match.source = source;
match.type = type;
match.dontgib = dontgib;
}
static clearscope int GetAmount( Actor victim )
{
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
SWWMDamageAccumulator a, match = null;
while ( a = SWWMDamageAccumulator(ti.Next()) )
{
if ( a.victim != victim ) continue;
return a.total;
}
return 0;
}
}
Class SpreadgunTracer : LineTracer
{
Actor ignoreme;
Array<HitListEntry> hitlist;
Array<Line> shootthroughlist;
Array<WaterHit> waterhitlist;
override ETraceStatus TraceCallback()
{
// liquid splashes
if ( Results.CrossedWater )
{
let hl = new("WaterHit");
hl.sect = Results.CrossedWater;
hl.hitpos = Results.CrossedWaterPos;
WaterHitList.Push(hl);
}
else if ( Results.Crossed3DWater )
{
let hl = new("WaterHit");
hl.sect = Results.Crossed3DWater;
hl.hitpos = Results.Crossed3DWaterPos;
WaterHitList.Push(hl);
}
if ( Results.HitType == TRACE_HitActor )
{
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
if ( Results.HitActor.bSHOOTABLE )
{
int amt = SWWMDamageAccumulator.GetAmount(Results.HitActor);
// getgibhealth isn't clearscope, fuck
int gibhealth = -int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor);
if ( Results.HitActor.GibHealth != int.min ) gibhealth = -abs(Results.HitActor.GibHealth);
// if gibbed, go through without dealing more damage
if ( Results.HitActor.health-amt <= gibhealth ) return TRACE_Skip;
let ent = new("HitListEntry");
ent.hitactor = Results.HitActor;
ent.hitlocation = Results.HitPos;
ent.x = Results.HitVector;
hitlist.Push(ent);
// go right on through if dead
if ( Results.HitActor.health-amt <= 0 ) return TRACE_Skip;
// stap
return TRACE_Abort;
}
return TRACE_Skip;
}
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
{
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
return TRACE_Stop;
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
return TRACE_Stop;
}
}
Class SpreadSlugTracer : SpreadgunTracer
{
double penetration; // please don't laugh
override ETraceStatus TraceCallback()
{
// liquid splashes
if ( Results.CrossedWater )
{
let hl = new("WaterHit");
hl.sect = Results.CrossedWater;
hl.hitpos = Results.CrossedWaterPos;
WaterHitList.Push(hl);
}
else if ( Results.Crossed3DWater )
{
let hl = new("WaterHit");
hl.sect = Results.Crossed3DWater;
hl.hitpos = Results.Crossed3DWaterPos;
WaterHitList.Push(hl);
}
if ( Results.HitType == TRACE_HitActor )
{
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
if ( Results.HitActor.bSHOOTABLE )
{
let ent = new("HitListEntry");
ent.hitactor = Results.HitActor;
ent.hitlocation = Results.HitPos;
ent.x = Results.HitVector;
ent.hitdamage = min(Results.HitActor.health+int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor),int(penetration));
hitlist.Push(ent);
penetration = max(0,penetration-ent.hitdamage);
if ( penetration <= 0 ) return TRACE_Abort;
return TRACE_Skip;
}
return TRACE_Skip;
}
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
{
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
return TRACE_Stop;
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
return TRACE_Stop;
}
}
Class SpreadImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("spreadgun/pellet",CHAN_VOICE,CHANF_DEFAULT,.4,4.);
A_SprayDecal("TinyBulletChip",-20);
int numpt = Random[Spreadgun](-2,2);
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](-3,3);
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,2);
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 SlugImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("spreadgun/slug",CHAN_VOICE,CHANF_DEFAULT,1.,2.);
A_SprayDecal("BulletChip",-20);
int numpt = Random[Spreadgun](6,12);
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.special1 = Random[Spreadgun](0,2);
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
}
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("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Spreadgun](10,15);
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 Spreadgun : SWWMWeapon
{
bool fired; // shell was used
Class<Ammo> loadammo, nextammo; // currently loaded shell, next shell to load
transient ui TextureID WeaponBox, AmmoIcon[7], LoadedIcon[7];
transient ui Font TewiFont;
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( loadammo is 'RedShell' ) return StringTable.Localize("$O_SPREADGUN_RED");
if ( loadammo is 'GreenShell' ) return StringTable.Localize("$O_SPREADGUN_GREEN");
if ( loadammo is 'WhiteShell' ) return StringTable.Localize("$O_SPREADGUN_WHITE");
if ( loadammo is 'BlueShell' ) return StringTable.Localize("$O_SPREADGUN_BLUE");
if ( loadammo is 'BlackShell' ) return StringTable.Localize("$O_SPREADGUN_BLACK");
if ( loadammo is 'PurpleShell' ) return StringTable.Localize("$O_SPREADGUN_PURPLE");
if ( loadammo is 'GoldShell' ) return StringTable.Localize("$O_SPREADGUN_GOLD");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
if ( !WeaponBox )
{
WeaponBox = TexMan.CheckForTexture("graphics/HUD/SpreadgunDisplay.png",TexMan.Type_Any);
AmmoIcon[0] = TexMan.CheckForTexture("graphics/HUD/RedShell.png",TexMan.Type_Any);
AmmoIcon[1] = TexMan.CheckForTexture("graphics/HUD/GreenShell.png",TexMan.Type_Any);
AmmoIcon[2] = TexMan.CheckForTexture("graphics/HUD/WhiteShell.png",TexMan.Type_Any);
AmmoIcon[3] = TexMan.CheckForTexture("graphics/HUD/BlueShell.png",TexMan.Type_Any);
AmmoIcon[4] = TexMan.CheckForTexture("graphics/HUD/BlackShell.png",TexMan.Type_Any);
AmmoIcon[5] = TexMan.CheckForTexture("graphics/HUD/PurpleShell.png",TexMan.Type_Any);
AmmoIcon[6] = TexMan.CheckForTexture("graphics/HUD/GoldShell.png",TexMan.Type_Any);
LoadedIcon[0] = TexMan.CheckForTexture("graphics/HUD/LoadedRedShell.png",TexMan.Type_Any);
LoadedIcon[1] = TexMan.CheckForTexture("graphics/HUD/LoadedGreenShell.png",TexMan.Type_Any);
LoadedIcon[2] = TexMan.CheckForTexture("graphics/HUD/LoadedWhiteShell.png",TexMan.Type_Any);
LoadedIcon[3] = TexMan.CheckForTexture("graphics/HUD/LoadedBlueShell.png",TexMan.Type_Any);
LoadedIcon[4] = TexMan.CheckForTexture("graphics/HUD/LoadedBlackShell.png",TexMan.Type_Any);
LoadedIcon[5] = TexMan.CheckForTexture("graphics/HUD/LoadedPurpleShell.png",TexMan.Type_Any);
LoadedIcon[6] = TexMan.CheckForTexture("graphics/HUD/LoadedGoldShell.png",TexMan.Type_Any);
}
if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded');
Screen.DrawTexture(WeaponBox,false,bx-55,by-44,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int ox = 7;
int oy = 12;
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 = 22;
ox = 34;
}
}
for ( int i=0; i<7; i++ )
{
if ( loadammo != types[i] ) continue;
Screen.DrawTexture(LoadedIcon[i],false,bx-49,by-9,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fired?Color(128,0,0,0):Color(0,0,0,0));
break;
}
}
override bool ReportHUDAmmo()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
for ( int i=0; i<7; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
return !fired;
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
if ( (firemode == PrimaryFire) || (firemode == AltFire) )
{
if ( !fired ) return true;
for ( int i=0; i<7; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
return false;
}
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
}
override bool UsesAmmo( Class<Ammo> kind )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
for ( int i=0; i<7; i++ ) if ( kind is types[i] ) return true;
return false;
}
action void A_SelectUnloadState()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
static const statelabel primedstates[] = {"UnloadRed", "UnloadGreen", "UnloadWhite", "UnloadBlue", "UnloadBlack", "UnloadPurple", "UnloadGold"};
static const statelabel firedstates[] = {"UnloadRedFired", "UnloadGreenFired", "UnloadWhiteFired", "UnloadBlueFired", "UnloadBlackFired", "UnloadPurpleFired", "UnloadGoldFired"};
int amidx = 0;
for ( int i=0; i<7; i++ )
{
if ( invoker.loadammo != types[i] ) continue;
amidx = i;
break;
}
if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx]));
else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
A_Overlay(-9999,"UnloadDummy");
A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_SelectLoadState()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
static const statelabel primedstates[] = {"LoadRed", "LoadGreen", "LoadWhite", "LoadBlue", "LoadBlack", "LoadPurple", "LoadGold"};
static const statelabel firedstates[] = {"LoadRedFired", "LoadGreenFired", "LoadWhiteFired", "LoadBlueFired", "LoadBlackFired", "LoadPurpleFired", "LoadGoldFired"};
int amidx = 0;
for ( int i=0; i<7; i++ )
{
if ( invoker.nextammo != types[i] ) continue;
amidx = i;
break;
}
if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx]));
else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx]));
if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
{
TakeInventory(invoker.nextammo,1);
}
A_Overlay(-9999,"LoadDummy");
}
action void A_DropShell()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
static const Class<Actor> casetypes[] = {"RedShellCasing","GreenShellCasing","WhiteShellCasing","BlueShellCasing","BlackShellCasing","PurpleShellCasing","GoldShellCasing"};
if ( !invoker.fired )
{
for ( int i=0; i<7; i++ )
{
if ( invoker.loadammo != types[i] ) continue;
if ( !GiveInventory(types[i],1) ) Spawn(types[i],Vec3Angle(5,angle,height/2));
break;
}
}
else
{
for ( int i=0; i<7; i++ )
{
if ( invoker.loadammo != types[i] ) continue;
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-10*z);
let c = Spawn(casetypes[i],origin);
c.angle = angle;
c.pitch = pitch;
c.vel = x*FRandom[Junk](-.2,.2)+y*FRandom[Junk](-.2,.2)-(0,0,FRandom[Junk](2,3));
c.vel += vel*.5;
break;
}
}
}
action void ProcessTraceHit( SpreadgunTracer t, Vector3 origin, Vector3 dir, int dmg, double mm, Class<Actor> impact = "SpreadImpact", int bc = 1, bool large = false )
{
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
t.ShootThroughList[i].Activate(self,0,SPAC_PCross);
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Spawn(large?"InvisibleSplasher":"SmolInvisibleSplasher",t.WaterHitList[i].hitpos);
b.A_CheckTerrain();
}
for ( int i=5; i<t.Results.Distance; i+=10 )
{
if ( !Random[Boolet](0,bc) ) continue;
let b = Actor.Spawn("SWWMBubble",level.Vec3Offset(origin,dir*i));
b.Scale *= FRandom[Boolet](.1,.3);
}
for ( int i=0; i<t.HitList.Size(); i++ )
{
int realdmg = dmg?dmg:t.HitList[i].HitDamage;
SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,invoker,self,'shot',!large);
SWWMHandler.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),mm*FRandom[Spreadgun](0.4,1.2));
if ( t.HitList[i].HitActor.bNOBLOOD || t.HitList[i].HitActor.bINVULNERABLE )
{
let p = Spawn(impact,t.HitList[i].HitLocation);
p.angle = atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180;
p.pitch = asin(t.HitList[i].x.z);
}
else
{
t.HitList[i].HitActor.TraceBleed(realdmg,self);
t.HitList[i].HitActor.SpawnBlood(t.HitList[i].HitLocation,atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180,realdmg);
if ( large ) t.HitList[i].HitActor.A_StartSound("spreadgun/slugf",CHAN_BODY,CHANF_OVERLAP,1.,2.);
else t.HitList[i].HitActor.A_StartSound("spreadgun/pelletf",CHAN_BODY,CHANF_OVERLAP,.4,4.);
}
}
if ( t.Results.HitType != TRACE_HitNone )
{
Vector3 hitnormal = -t.Results.HitVector;
if ( t.Results.HitType == TRACE_HitFloor )
{
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.top.Normal;
else hitnormal = t.Results.HitSector.floorplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.bottom.Normal;
else hitnormal = t.Results.HitSector.ceilingplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitWall )
{
hitnormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
if ( !t.Results.Side ) hitnormal *= -1;
}
let p = Spawn(impact,t.Results.HitPos+hitnormal*4);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
if ( t.Results.HitLine ) t.Results.HitLine.RemoteActivate(self,t.Results.Side,SPAC_Impact,t.Results.HitPos);
}
}
action void A_FireShell()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
static const statelabel flashes[] = {"FlashRed","FlashGreen","FlashWhite","FlashBlue","FlashBlack","FlashPurple","FlashGold"};
static const String sounds[] = {"spreadgun/redfire","spreadgun/greenfire","spreadgun/whitefire","spreadgun/bluefire","spreadgun/blackfire","spreadgun/purplefire","spreadgun/goldfire"};
static const int louds[] = {800,1000,1100,1200,1400,600,2500};
static const int quakes[] = {3,4,2,4,3,1,6};
static const Color cols[] = {Color(40,255,192,64),Color(36,255,192,80),Color(64,255,160,32),Color(48,32,176,255),Color(72,255,128,16),Color(24,255,224,96),Color(96,255,224,16)};
for ( int i=0; i<7; i++ )
{
if ( invoker.loadammo != types[i] ) continue;
A_SWWMFlash(flashes[i]);
A_StartSound(sounds[i],CHAN_WEAPON,CHANF_OVERLAP,attenuation:.6);
A_AlertMonsters(louds[i]);
A_QuakeEx(quakes[i],quakes[i],quakes[i],9,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.2*quakes[i]);
A_ZoomFactor(1.+quakes[i]*.04,ZOOM_INSTANT);
A_ZoomFactor(1.);
SWWMHandler.DoFlash(self,cols[i],5);
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-2*z);
Vector3 x2, y2, z2;
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
double a, s;
Vector3 dir;
SpreadgunTracer st;
SpreadSlugTracer sst;
switch ( i )
{
case 1:
sst = new("SpreadSlugTracer");
sst.ignoreme = self;
sst.penetration = 250.;
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.01);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
sst.hitlist.Clear();
sst.shootthroughlist.Clear();
sst.waterhitlist.Clear();
sst.Trace(origin,level.PointInSector(origin.xy),dir,8000.,0);
ProcessTraceHit(sst,origin,dir,0,12000,"SlugImpact",1,true);
for ( int i=0; i<3; i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
s.alpha *= 0.5;
}
for ( int i=0; i<6; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .8;
s.alpha *= .3;
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
}
for ( int i=0; i<10; i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .2;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
}
break;
case 2:
Console.Printf("\cg// TODO Dragon's Breath\c-");
break;
case 3:
Console.Printf("\cg// TODO Kinylum Saltshot\c-");
break;
case 4:
Console.Printf("\cg// TODO Napalm Rounds\c-");
break;
case 5:
Console.Printf("\cg// TODO The Ball\c-");
break;
case 6:
Console.Printf("\cg// TODO Golden Shell\c-");
break;
default:
st = new("SpreadgunTracer");
st.ignoreme = self;
for ( int j=0; j<30; j++ )
{
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.22);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
st.hitlist.Clear();
st.shootthroughlist.Clear();
st.waterhitlist.Clear();
st.Trace(origin,level.PointInSector(origin.xy),dir,8000.,0);
ProcessTraceHit(st,origin,dir,6,7000,bc:5);
}
for ( int i=0; i<9; i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
s.alpha *= .2;
}
for ( int i=0; i<16; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.special1 = 1;
s.scale *= .9;
s.alpha *= .3;
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
}
for ( int i=0; i<20; i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .3;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
}
break;
}
break;
}
A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP);
invoker.fired = true;
}
action void A_LoadShell()
{
A_StartSound("spreadgun/shellin",CHAN_WEAPON,CHANF_OVERLAP);
invoker.loadammo = invoker.nextammo;
}
action void A_Prime()
{
if ( invoker.fired )
{
A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP);
invoker.fired = false;
}
if ( CountInv(invoker.nextammo) <= 0 ) A_SwitchAmmoType(true);
}
override void AttachToOwner( Actor other )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
Super.AttachToOwner(other);
if ( !loadammo ) loadammo = "RedShell";
for ( int i=0; i<7; i++ )
{
Ammo a = Ammo(other.FindInventory(types[i]));
if ( !a ) continue;
nextammo = types[i];
return;
}
nextammo = AmmoType1;
}
action void A_SwitchAmmoType( bool rev = false )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"};
int cur = 0, next = 0;
for ( int i=0; i<7; i++ )
{
if ( invoker.nextammo != types[i] ) continue;
cur = i;
break;
}
for ( int i=0; i<7; i++ )
{
int ridx;
if ( rev )
{
ridx = cur-i;
if ( ridx < 0 ) break;
}
else ridx = (i+cur+1)%7;
if ( CountInv(types[ridx]) <= 0 ) continue;
next = ridx;
break;
}
if ( rev && (invoker.nextammo == types[next]) )
{
A_SwitchAmmoType(false); // recheck forward
return;
}
if ( invoker.nextammo != types[next] ) A_StartSound("misc/invchange",CHAN_WEAPONEXTRA,CHANF_UI|CHANF_LOCAL);
invoker.nextammo = types[next];
A_WeaponReady(WRF_NOFIRE);
}
action void A_AltHold()
{
A_WeaponReady(WRF_NOFIRE);
if ( player.cmd.buttons&BT_ALTATTACK ) return;
if ( !invoker.fired ) player.SetPSPrite(PSP_WEAPON,invoker.FindState("Ready"));
else player.SetPSPrite(PSP_WEAPON,invoker.FindState("ReadyFired"));
}
Default
{
Tag "$T_SPREADGUN";
Inventory.PickupMessage "$I_SPREADGUN";
Obituary "$O_SPREADGUN";
Weapon.UpSound "spreadgun/select";
Weapon.SlotNumber 3;
Weapon.SelectionOrder 2400;
Weapon.AmmoType1 "RedShell";
Weapon.AmmoGive1 1;
Stamina 15000;
+SWWMWEAPON.NOFIRSTGIVE;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Deselect:
XZW2 A 1
{
A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.fired,"DeselectFired");
}
XZW2 BCDEFGHI 1;
XZW2 I -1 A_FullLower();
Stop;
DeselectFired:
XZW2 Z 1;
XZW3 ABCDEFGH 1;
XZW3 H -1 A_FullLower();
Stop;
Select:
XZW2 I 1
{
A_FullRaise();
return A_JumpIf(invoker.fired,"SelectFired");
}
XZW2 JKLMNOPQ 1;
Goto Ready;
SelectFired:
XZW3 HIJKLMNOP 1;
Goto ReadyFired;
Ready:
XZW2 A 1
{
int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1;
if ( invoker.nextammo && (CountInv(invoker.nextammo) > 0) )
flg |= WRF_ALLOWRELOAD;
A_WeaponReady(flg);
}
Wait;
ReadyFired:
XZW2 Z 1
{
int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1;
if ( invoker.nextammo && (CountInv(invoker.nextammo) > 0) )
flg |= WRF_ALLOWRELOAD;
else flg |= WRF_NOPRIMARY;
A_WeaponReady(flg);
}
Wait;
Fire:
#### # 1
{
if ( invoker.fired ) return ResolveState("Reload");
A_FireShell();
return ResolveState(null);
}
XZW2 RSTU 1;
XZW2 VWXY 2;
Goto ReadyFired;
AltFire:
#### # 1 A_SwitchAmmoType();
#### # 1 A_AltHold();
Wait;
Reload:
#### # 1 A_SelectUnloadState();
Stop;
UnloadDummy: // overlay with shared functions for all unload anims
TNT1 A 11;
TNT1 A 14 A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP);
TNT1 A 1 A_DropShell();
Stop;
UnloadRedFired:
XZW2 Z 2;
XZW3 QRST 2;
XZW3 UVWXYZ 1;
XZW4 ABCDEFGH 1;
XZW8 M 1;
Goto Reload2;
UnloadGreenFired:
XZW2 Z 2;
XZW4 IJKL 2;
XZW4 MNOPQRSTUVWXYZ 1;
XZW9 T 1;
Goto Reload2;
UnloadWhiteFired:
XZW2 Z 2;
XZW5 ABCD 2;
XZW5 EFGHIJKLMNOPQR 1;
XZWB A 1;
Goto Reload2;
UnloadBlueFired:
XZW2 Z 2;
XZW5 STUV 2;
XZW5 WXYZ 1;
XZW6 ABCDEFGHIJ 1;
XZWC H 1;
Goto Reload2;
UnloadBlackFired:
XZW2 Z 2;
XZW6 KLMN 2;
XZW6 OPQRSTUVWXYZ 1;
XZW7 AB 1;
XZWD O 1;
Goto Reload2;
UnloadPurpleFired:
XZW2 Z 2;
XZW7 CDEF 2;
XZW7 GHIJKLMNOPQRST 1;
XZWE V 1;
Goto Reload2;
UnloadGoldFired:
XZW2 Z 2;
XZW7 UVWX 2;
XZW7 YZ 1;
XZW8 ABCDEFGHIJKL 1;
XZWG C 1;
Goto Reload2;
UnloadRed:
XZW2 A 2;
XZWK JKLM 2;
XZWK NOPQRSTUVWXYZ 1;
XZWL A 1;
XZWP F 1;
Goto Reload2;
UnloadGreen:
XZW2 A 2;
XZWL BCDE 2;
XZWL FGHIJKLMNOPQRS 1;
XZWQ M 1;
Goto Reload2;
UnloadWhite:
XZW2 A 2;
XZWL TUVW 2;
XZWL XYZ 1;
XZWM ABCDEFGHIJK 1;
XZWR T 1;
Goto Reload2;
UnloadBlue:
XZW2 A 2;
XZWM LMNO 2;
XZWM PQRSTUVWXYZ 1;
XZWN ABC 1;
XZWT A 1;
Goto Reload2;
UnloadBlack:
XZW2 A 2;
XZWN DEFG 2;
XZWN HIJKLMNOPQRSTU 1;
XZWU H 1;
Goto Reload2;
UnloadPurple:
XZW2 A 2;
XZWN VWXY 2;
XZWN Z 1;
XZWO ABCDEFGHIJKLM 1;
XZWV O 1;
Goto Reload2;
UnloadGold:
XZW2 A 2;
XZWO NOPQ 2;
XZWO RSTUVWXYZ 1;
XZWP ABCDE 1;
XZWW V 1;
Goto Reload2;
Reload2:
#### # 1 A_SelectLoadState();
Stop;
LoadDummy: // overlay with shared functions for all load anims
TNT1 A 9;
TNT1 A 12 A_LoadShell();
TNT1 A 2 A_StartSound("spreadgun/close",CHAN_WEAPON,CHANF_OVERLAP);
TNT1 A 2 A_Prime();
TNT1 A 1 { invoker.PlayUpSound(self); }
Stop;
LoadRedFired:
XZW8 MNOPQRSTUVWXYZ 1;
XZW9 ABCDEFGHIJKLMNOPQRS 1;
Goto Ready;
LoadGreenFired:
XZW9 TUVWXYZ 1;
XZWA ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
Goto Ready;
LoadWhiteFired:
XZWB ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
XZWC ABCDEFG 1;
Goto Ready;
LoadBlueFired:
XZWC HIJKLMNOPQRSTUVWXYZ 1;
XZWD ABCDEFGHIJKLMN 1;
Goto Ready;
LoadBlackFired:
XZWD OPQRSTUVWXYZ 1;
XZWE ABCDEFGHIJKLMNOPQRSTU 1;
Goto Ready;
LoadPurpleFired:
XZWE VWXYZ 1;
XZWF ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
XZWG AB 1;
Goto Ready;
LoadGoldFired:
XZWG CDEFGHIJKLMNOPQRSTUVWXYZ 1;
XZWH ABCDEFGHI 1;
Goto Ready;
LoadRed:
XZWP FGHIJKLMNOPQRSTUVWXYZ 1;
XZWQ ABCDEFGHIJKL 1;
Goto Ready;
LoadGreen:
XZWQ MNOPQRSTUVWXYZ 1;
XZWR ABCDEFGHIJKLMNOPQRS 1;
Goto Ready;
LoadWhite:
XZWR TUVWXYZ 1;
XZWS ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
Goto Ready;
LoadBlue:
XZWT ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
XZWU ABCDEFG 1;
Goto Ready;
LoadBlack:
XZWU HIJKLMNOPQRSTUVWXYZ 1;
XZWV ABCDEFGHIJKLMN 1;
Goto Ready;
LoadPurple:
XZWV OPQRSTUVWXYZ 1;
XZWW ABCDEFGHIJKLMNOPQRSTU 1;
Goto Ready;
LoadGold:
XZWW VWXYZ 1;
XZWX ABCDEFGHIJKLMNOPQRSTUVWXYZ 1;
XZWY AB 1;
Goto Ready;
Zoom:
XZW2 A 1
{
A_StartSound("spreadgun/checkgun",CHAN_WEAPON,CHANF_OVERLAP);
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;
TNT1 A 1 A_Melee();
Stop;
User1:
XZW2 A 2
{
A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.fired,"User1Fired");
}
XZWI JK 2;
User1Hold:
XZWI L 1
{
A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP);
A_Overlay(-9999,"DummyMelee");
}
XZWI MNOP 2;
XZWI QR 3;
XZWI S 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1Hold");
XZWI S 0 { invoker.PlayUpSound(self); }
XZWI STUV 2;
Goto Ready;
User1Fired:
XZW2 Z 2;
XZWJ WX 2;
User1FiredHold:
XZWJ Y 1
{
A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP);
A_Overlay(-9999,"DummyMelee");
}
XZWJ Z 2;
XZWK ABC 2;
XZWK DE 3;
XZWK F 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1FiredHold");
XZWK F 0 { invoker.PlayUpSound(self); }
XZWK FGHI 2;
Goto ReadyFired;
FlashRed:
XZWZ A 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 120;
l.target = self;
}
Stop;
FlashGreen:
XZWZ B 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 90;
l.target = self;
}
Stop;
FlashWhite:
XZWZ C 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[1] = 176;
l.args[2] = 32;
l.args[3] = 160;
l.target = self;
}
Stop;
FlashBlue:
XZWZ D 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[0] = 96;
l.args[1] = 224;
l.args[2] = 255;
l.args[3] = 160;
l.target = self;
}
Stop;
FlashBlack:
XZWZ E 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[1] = 144;
l.args[2] = 16;
l.args[3] = 280;
l.target = self;
}
Stop;
FlashPurple:
XZWZ F 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 60;
l.target = self;
}
Stop;
FlashGold:
XZWZ G 2 Bright
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = 300;
l.target = self;
}
Stop;
}
}