Partial implementation of Fuck Your Shit rounds, currently in progress. Added various shader effects to some powerups, and to player damage. Added custom view effects to player death, disabled "face attacker" because it looks weird with model-based players. Added "untouchable" spree tracking to the Stats tab. Implemented "emergency reboot system" for people who want a less shameful form of the Resurrect cheat. Cooldown for consecutive reboots can be configured. Rebalanced armors. Small language string corrections. Adjusted pickup model sizes of some weapons. Fixed missing punch sound (damn typos). Fix targetter always displaying voodoo dolls. Fix uptime breaking when loading saves, now based on total playtime rather than gametic. Readjusted Spreadgun ammo availability. Flush HUD interpolators alongside messages, fixes things such as the score VERY slowly counting up when loading a save. Spare armors now only get auto-used on pickup if there is NO armor available of that type. Added some extra visual effects to punching walls and non-bleeding actors. Slightly altered the melee range so it's not as awkward. Fixed punching not using flesh sounds for bleeding actors. Pusher primary now drags the player towards their target, like the Chainsaw. Fixed the player having no pain sounds whatsoever. Fixed stair step anchoring not working. The damage dealt when walljumping on a monster now also gets boosted by the Ragekit.
2321 lines
60 KiB
Text
2321 lines
60 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,4);
|
|
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);
|
|
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,5);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](1,4);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class SlugImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
}
|
|
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](-.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](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 DragonBreathPuff : Actor
|
|
{
|
|
Vector2 initsc;
|
|
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Scale 0.5;
|
|
Alpha 0.35;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
+NOTELEPORT;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
}
|
|
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';
|
|
+NOBLOCKMAP;
|
|
+THRUACTORS;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+USEBOUNCESTATE;
|
|
+MISSILE;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+NOTELEPORT;
|
|
-NOGRAVITY;
|
|
Gravity 0.15;
|
|
BounceFactor 1.0;
|
|
Radius 4;
|
|
Height 4;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
reactiontime = Random[ExploS](18,24);
|
|
vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*FRandom[ExploS](16.,24.);
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
Vector3 HitNormal = -vel.unit();
|
|
F3DFloor ff;
|
|
if ( BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.top.Normal;
|
|
else HitNormal = BlockingFloor.floorplane.Normal;
|
|
}
|
|
else if ( BlockingCeiling )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.bottom.Normal;
|
|
else HitNormal = BlockingCeiling.ceilingplane.Normal;
|
|
}
|
|
else if ( BlockingLine )
|
|
{
|
|
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
|
|
HitNormal *= -1;
|
|
}
|
|
else if ( BlockingMobj )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(pos,BlockingMobj.pos);
|
|
if ( (pos.x+radius) <= (BlockingMobj.pos.x-BlockingMobj.radius) )
|
|
HitNormal = (-1,0,0);
|
|
else if ( (pos.x-radius) >= (BlockingMobj.pos.x+BlockingMobj.radius) )
|
|
HitNormal = (1,0,0);
|
|
else if ( (pos.y+radius) <= (BlockingMobj.pos.y-BlockingMobj.radius) )
|
|
HitNormal = (0,-1,0);
|
|
else if ( (pos.y-radius) >= (BlockingMobj.pos.y+BlockingMobj.radius) )
|
|
HitNormal = (0,1,0);
|
|
else if ( pos.z >= (BlockingMobj.pos.z+BlockingMobj.height) )
|
|
HitNormal = (0,0,1);
|
|
else if ( (pos.z+height) <= BlockingMobj.pos.z )
|
|
HitNormal = (0,0,-1);
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
vel = .8*((vel dot HitNormal)*HitNormal*(-1.8+FRandom[Spreadgun](.0,.8))+vel);
|
|
bHITOWNER = true;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1
|
|
{
|
|
if ( waterlevel > 0 ) ReactionTime -= 2;
|
|
A_CountDown();
|
|
let p = Spawn("DragonBreathPuff",pos);
|
|
p.alpha *= .6+.4*(ReactionTime/20.);
|
|
p.scale *= 3.5-2.5*(ReactionTime/20.);
|
|
if ( !(ReactionTime%3) )
|
|
{
|
|
let l = Spawn("PaletteLight",pos);
|
|
l.Args[3] = int(90+50*(ReactionTime/20.));
|
|
l.ReactionTime = int(2+8*(ReactionTime/20.));
|
|
l.target = p;
|
|
}
|
|
if ( !(GetAge()%2) )
|
|
{
|
|
SWWMHandler.DoBlast(self,150-6*reactiontime,1000+200*reactiontime,bHITOWNER?null:target);
|
|
A_Explode(1+reactiontime/2,150-6*reactiontime,bHITOWNER?XF_HURTSOURCE:0);
|
|
}
|
|
double spd = vel.length();
|
|
vel = (vel*.4+(FRandom[ExploS](-.2,.2),FRandom[ExploS](-.2,.2),FRandom[ExploS](-.2,.2))).unit()*spd;
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,5);
|
|
if ( !(ReactionTime%2) )
|
|
{
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel+vel*.2;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](96,192));
|
|
s.special1 = Random[ExploS](2,4);
|
|
s.scale *= 2.4;
|
|
s.alpha *= .1+.2*(ReactionTime/20.);
|
|
int numpt = Random[Spreadgun](-2,4);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = vel+(FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,4);
|
|
let s2 = Spawn("SWWMSpark",pos);
|
|
s2.scale *= .4;
|
|
s2.vel = pvel;
|
|
}
|
|
}
|
|
}
|
|
Wait;
|
|
Bounce:
|
|
TNT1 A 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
}
|
|
}
|
|
|
|
Class SaltTracer : LineTracer
|
|
{
|
|
Actor ignore;
|
|
Array<Line> ShootThroughList;
|
|
Array<WaterHit> WaterHitList;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.CrossedWater;
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.Crossed3DWater;
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignore ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BlockHitscan) )
|
|
return TRACE_Stop;
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SaltLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SaltTail";
|
|
ReactionTime 30;
|
|
Args 0,0,0,120;
|
|
}
|
|
}
|
|
Class SaltLight2 : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SaltExpl";
|
|
ReactionTime 30;
|
|
Args 0,0,0,70;
|
|
}
|
|
}
|
|
|
|
Class SaltImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_BLUE";
|
|
DamageType "Electricity";
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
Scale 1.8;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_AlertMonsters();
|
|
SWWMHandler.DoBlast(self,120,15000);
|
|
A_Explode(25,120);
|
|
A_QuakeEx(3,3,3,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN,falloff:150,rollintensity:0.2);
|
|
A_StartSound("spreadgun/salt",CHAN_VOICE,attenuation:.35);
|
|
A_SprayDecal("ShockMarkSmall",-172);
|
|
A_SprayDecal("SaltMark",-172);
|
|
Scale *= FRandom[ExploS](0.8,1.1);
|
|
int numpt = Random[ExploS](5,9);
|
|
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);
|
|
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);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Spawn("SaltLight2",pos);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 0 NoDelay A_Jump(256,"Expl1","Expl2","Expl3");
|
|
Expl1:
|
|
KSX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
Expl2:
|
|
KSX2 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
Expl3:
|
|
KSX3 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SaltBeam : Actor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_BLUE";
|
|
DamageType "Electricity";
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
Stamina 3;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
}
|
|
|
|
void SpreadOut()
|
|
{
|
|
A_AlertMonsters(1200);
|
|
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);
|
|
for ( int i=0; i<t.WaterHitList.Size(); i++ )
|
|
{
|
|
let b = Actor.Spawn("InvisibleSplasher",t.WaterHitList[i].hitpos);
|
|
b.A_CheckTerrain();
|
|
}
|
|
for ( int i=16; i<t.Results.Distance; i+=32 )
|
|
{
|
|
if ( !Random[Spreadgun](0,Stamina) ) continue;
|
|
let b = Actor.Spawn("SWWMSmoke",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 ( t.Results.HitType == TRACE_HitActor )
|
|
{
|
|
SWWMHandler.DoKnockback(t.Results.HitActor,x,25000);
|
|
t.Results.HitActor.DamageMobj(self,target,60,'Salt',DMG_THRUSTLESS);
|
|
}
|
|
Vector3 norm = -x;
|
|
if ( t.Results.HitType == TRACE_HitWall )
|
|
{
|
|
norm = (t.Results.HitLine.delta.y,-t.Results.HitLine.delta.x,0).unit();
|
|
if ( t.Results.Side ) norm *= -1;
|
|
t.Results.HitLine.RemoteActivate(tracer,t.Results.Side,SPAC_Impact,t.Results.HitPos);
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( t.Results.ffloor ) norm = -t.Results.ffloor.top.Normal;
|
|
else norm = t.Results.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( t.Results.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( t.Results.ffloor ) norm = -t.Results.ffloor.bottom.Normal;
|
|
else norm = t.Results.HitSector.ceilingplane.Normal;
|
|
}
|
|
if ( t.Results.HitType != TRACE_HasHitSky )
|
|
{
|
|
let i = Spawn("SaltImpact",level.Vec3Offset(t.Results.HitPos,norm*4));
|
|
i.angle = atan2(norm.y,norm.x);
|
|
i.pitch = asin(-norm.z);
|
|
i.target = target;
|
|
}
|
|
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;
|
|
return;
|
|
}
|
|
// next beam
|
|
if ( !(special2%2) && !Random[Spreadgun](0,4) )
|
|
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.SetStateLabel("TrailSpawn");
|
|
}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("spreadgun/salttrail",CHAN_VOICE,CHANF_DEFAULT,.3,4.);
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
A_FadeOut(.04);
|
|
if ( Random[Spreadgun](-2,GetAge()/10) == 0 )
|
|
{
|
|
SWWMHandler.DoBlast(self,64,5000,target);
|
|
A_Explode(5,64,0);
|
|
}
|
|
if ( ((special2%5) || GetAge()) && !special1 ) SpreadOut();
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1 Bright NoDelay
|
|
{
|
|
return FindState("StarterDev")+Random[Spreadgun](0,11)*2;
|
|
}
|
|
Stop;
|
|
TrailSpawn:
|
|
XZW2 A -1 Bright
|
|
{
|
|
return FindState("TrailerDev")+Random[Spreadgun](0,11)*2;
|
|
}
|
|
Stop;
|
|
StarterDev:
|
|
#### # 25 Bright;
|
|
XZW1 B -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 C -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 D -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 E -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 F -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 G -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 H -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 I -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 J -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 K -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 L -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 M -1 Bright;
|
|
Stop;
|
|
TrailerDev:
|
|
#### # 25 Bright;
|
|
XZW2 B -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 C -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 D -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 E -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 F -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 G -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 H -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 I -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 J -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 K -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 L -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 M -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class OnFireLight : DynamicLight
|
|
{
|
|
OnFire of;
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !of || !of.victim )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Args[0] = clamp(of.Amount*4,0,255);
|
|
Args[1] = clamp(of.Amount*2,0,128);
|
|
Args[3] = int(max(of.victim.radius,of.victim.height)*2.+20+clamp(of.amount/5,0,80));
|
|
SetOrigin(of.Victim.Vec3Offset(0,0,of.Victim.Height/2),true);
|
|
}
|
|
}
|
|
|
|
Class OnFire : Actor
|
|
{
|
|
Actor victim, instigator, lite;
|
|
int amount, cnt, delay;
|
|
double oangle;
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !victim )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( victim.waterlevel > 0 )
|
|
{
|
|
if ( lite ) lite.Destroy();
|
|
amount -= int(victim.waterlevel**2);
|
|
}
|
|
if ( (victim.Health <= 0) || ((victim is 'FlamingChunk') && !victim.bMISSILE) ) amount = min(amount,100);
|
|
if ( !(level.maptime%3) )
|
|
amount--;
|
|
if ( victim.player ) amount -= int(abs(actor.deltaangle(victim.angle,oangle))/30);
|
|
oangle = victim.angle;
|
|
if ( amount < -30 )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( cnt > 0 ) cnt--;
|
|
else
|
|
{
|
|
cnt = 10;
|
|
if ( victim.bSHOOTABLE && (victim.Health > 0) && (amount > 0) )
|
|
victim.DamageMobj(self,instigator,clamp(int(amount*(victim.bBOSS?0.05:0.15)),1,20),'Fire',DMG_THRUSTLESS); // the only reason why we need to use an actor
|
|
if ( !victim )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
}
|
|
if ( delay > 0 ) delay--;
|
|
if ( level.maptime%5 ) return;
|
|
int numpt = clamp(int(Random[FlameT](2,4)*amount*0.02),1,4);
|
|
double mult = max(victim.radius,victim.height)/30.;
|
|
numpt = int(clamp(numpt*mult**.5,1,5));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pos = victim.Vec3Offset(FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](victim.height*0.2,victim.height*0.8));
|
|
double ang = FRandom[FlameT](0,360);
|
|
double pt = FRandom[FlameT](-90,90);
|
|
if ( amount > 0 )
|
|
{
|
|
let c = victim.Spawn("OnFireTrail",pos);
|
|
c.scale *= max(.3,mult*0.5);
|
|
c.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.5,2.)*c.scale.x;
|
|
}
|
|
let s = victim.Spawn("SWWMSmoke",pos);
|
|
s.scale *= max(1.,1.6*mult);
|
|
s.alpha *= min(amount+30,100)*0.02;
|
|
s.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.2,.6)*s.scale.x;
|
|
}
|
|
if ( amount <= 0 ) return;
|
|
// spread to nearby actors
|
|
let bt = BlockThingsIterator.Create(victim);
|
|
while ( bt.Next() )
|
|
{
|
|
let t = bt.Thing;
|
|
if ( !t || !t.bSHOOTABLE || (t.Health <= 0) || (t == victim) || ((t == instigator) && (delay > 0)) || (victim.Distance3D(t) > victim.radius+t.radius+20) || !victim.CheckSight(t) ) continue;
|
|
int amt = max(1,amount/10);
|
|
if ( IsOnFire(t) ) amt = min(5,amt);
|
|
Apply(t,instigator,amt);
|
|
}
|
|
}
|
|
|
|
static OnFire Apply( Actor victim, Actor instigator, int amount, int delay = 0 )
|
|
{
|
|
if ( amount <= 0 ) return null;
|
|
let ti = ThinkerIterator.Create("OnFire");
|
|
OnFire t;
|
|
while ( t = OnFire(ti.Next()) )
|
|
{
|
|
if ( t.victim != victim ) continue;
|
|
if ( instigator ) t.instigator = instigator;
|
|
t.amount = min(500,t.amount+amount);
|
|
t.cnt = min(t.cnt,5);
|
|
return t;
|
|
}
|
|
t = OnFire(Spawn("OnFire"));
|
|
t.victim = victim;
|
|
t.instigator = instigator;
|
|
t.amount = min(500,amount);
|
|
t.cnt = 1;
|
|
// for chunks
|
|
t.delay = delay;
|
|
t.lite = Actor.Spawn("OnFireLight",victim.pos);
|
|
OnFireLight(t.lite).of = t;
|
|
t.oangle = victim.angle;
|
|
return t;
|
|
}
|
|
|
|
static bool IsOnFire( Actor victim )
|
|
{
|
|
let ti = ThinkerIterator.Create("OnFire");
|
|
OnFire t;
|
|
while ( t = OnFire(ti.Next()) )
|
|
{
|
|
if ( t.victim != victim ) continue;
|
|
return (t.amount>0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
Obituary "$O_SPREADGUN_BLACK";
|
|
}
|
|
}
|
|
|
|
Class OnFireTrailLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "HellExpl";
|
|
Args 0,0,0,40;
|
|
ReactionTime 40;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
Args[0] /= 10;
|
|
Args[1] /= 10;
|
|
Args[2] /= 10;
|
|
Args[3] += 3;
|
|
if ( !target || (target.waterlevel > 0) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
}
|
|
}
|
|
|
|
Class OnFireTrail : Actor
|
|
{
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
Scale.x *= RandomPick[ExploS](-1,1);
|
|
Scale.y *= RandomPick[ExploS](-1,1);
|
|
roll = FRandom[ExploS](0,360);
|
|
}
|
|
action void A_Flame()
|
|
{
|
|
if ( waterlevel > 0 )
|
|
vel *= 0.9;
|
|
else
|
|
{
|
|
vel *= 0.98;
|
|
vel.z += 0.2*abs(scale.x);
|
|
}
|
|
if ( waterlevel > 0 )
|
|
{
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = (FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2));
|
|
s.vel += vel*0.3;
|
|
s.alpha *= alpha*4;
|
|
s.scale *= 0.5+abs(scale.x)*(.5+GetAge()/6.);
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( !Random[FlameT](0,int(40*(default.alpha-alpha))) )
|
|
{
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = (FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2));
|
|
s.vel += vel*0.3;
|
|
s.alpha *= alpha*4;
|
|
s.scale *= 0.5+abs(scale.x)*(.5+GetAge()/6.);
|
|
}
|
|
}
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Speed 2;
|
|
Radius 4;
|
|
Height 4;
|
|
Alpha 0.3;
|
|
Scale 0.6;
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOFRICTION;
|
|
+SLIDESONWALLS;
|
|
+ACTIVATEPCROSS;
|
|
+ACTIVATEIMPACT;
|
|
+NOTELEPORT;
|
|
+FORCEXYBILLBOARD;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+DROPOFF;
|
|
+NOBLOCKMONST;
|
|
+DONTSPLASH;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XFLM ABCDEFGHIJKLMNOPQRST 1 Bright
|
|
{
|
|
A_Flame();
|
|
A_SetScale(scale.x*0.98);
|
|
A_FadeOut(0.01);
|
|
vel.z += 0.1;
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class FlamingChunk : Actor
|
|
{
|
|
double rollvel;
|
|
OnFire myfire;
|
|
Vector3 oldvel;
|
|
int deadtimer;
|
|
Actor lasthit;
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
|
|
Scale *= FRandom[FlameT](.8,1.2);
|
|
if ( waterlevel <= 0 ) myfire = OnFire.Apply(self,target,int(120*scale.x),6);
|
|
frame = Random[FlameT](0,5);
|
|
}
|
|
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
|
|
{
|
|
if ( target != lasthit )
|
|
{
|
|
OnFire.Apply(target,self.target,myfire?myfire.Amount:1);
|
|
lasthit = target;
|
|
}
|
|
return damage;
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( InStateSequence(CurState,ResolveState("Death")) )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
bHITOWNER = true;
|
|
lasthit = null;
|
|
Vector3 HitNormal = -vel.unit();
|
|
F3DFloor ff;
|
|
if ( BlockingFloor )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.top.Normal;
|
|
else HitNormal = BlockingFloor.floorplane.Normal;
|
|
}
|
|
else if ( BlockingCeiling )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.bottom.Normal;
|
|
else HitNormal = BlockingCeiling.ceilingplane.Normal;
|
|
}
|
|
else if ( BlockingLine )
|
|
{
|
|
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
|
|
HitNormal *= -1;
|
|
}
|
|
else if ( BlockingMobj )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
|
|
HitNormal = diff.unit();
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
vel = .8*((vel dot HitNormal)*HitNormal*-1.2+vel);
|
|
gravity = .35;
|
|
if ( (vel.length() < 5) && (pos.z <= floorz) )
|
|
{
|
|
ClearBounce();
|
|
ExplodeMissile();
|
|
}
|
|
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
|
|
}
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_BLACK";
|
|
DamageFunction 1;
|
|
DamageType 'Fire';
|
|
Radius 4;
|
|
Height 4;
|
|
Speed 15;
|
|
Gravity 0.2;
|
|
PROJECTILE;
|
|
-NOGRAVITY;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+EXPLODEONWATER;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+NODAMAGETHRUST;
|
|
+INTERPOLATEANGLES;
|
|
+RIPPER;
|
|
+BLOODLESSIMPACT;
|
|
+NOTELEPORT;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+USEBOUNCESTATE;
|
|
BounceFactor 1.0;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
JUNK # 1 { roll += rollvel; }
|
|
Wait;
|
|
Bounce:
|
|
JUNK # 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
Death:
|
|
JUNK # -1 { bMOVEWITHSECTOR = true; }
|
|
Stop;
|
|
Dummy:
|
|
JUNK ABCDEF -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class BallImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_QuakeEx(3,3,3,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.3);
|
|
A_StartSound("spreadgun/ball",CHAN_VOICE);
|
|
A_SprayDecal("WallCrack",-20);
|
|
int numpt = Random[Spreadgun](5,10);
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8))).unit()*FRandom[Spreadgun](.1,1.2);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](128,192));
|
|
}
|
|
numpt = Random[Spreadgun](4,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Spreadgun](4,8);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class TheBall : Actor
|
|
{
|
|
double heat;
|
|
int deadtimer;
|
|
Vector3 oldvel;
|
|
Actor lasthit;
|
|
|
|
Default
|
|
{
|
|
Obituary "$O_SPREADGUN_PURPLE";
|
|
+NOBLOCKMAP;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+USEBOUNCESTATE;
|
|
+MISSILE;
|
|
+NODAMAGETHRUST;
|
|
+NOTELEPORT;
|
|
-NOGRAVITY;
|
|
Speed 80;
|
|
Gravity 0.1;
|
|
BounceFactor 1.0;
|
|
Radius 2;
|
|
Height 2;
|
|
}
|
|
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);
|
|
// how hard this damn thing is going to slam
|
|
double slamforce = vel.length()*350.+heat*120;
|
|
SWWMHandler.DoKnockback(victim,vel.unit(),slamforce);
|
|
bool bleeds = (victim && !victim.bINVULNERABLE && !victim.bNOBLOOD && victim.bSHOOTABLE);
|
|
int dmg = int(vel.length()*4.2+heat*80);
|
|
dmg = victim.DamageMobj(self,target,dmg,'Concussion',DMG_THRUSTLESS);
|
|
Vector3 dir = -vel.unit();
|
|
// slam jam
|
|
if ( bleeds )
|
|
{
|
|
A_StartSound("spreadgun/ballf",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/75.)**.5);
|
|
victim.TraceBleed(dmg,self);
|
|
SpawnBlood(pos,atan2(dir.y,dir.x),dmg);
|
|
}
|
|
else
|
|
{
|
|
A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/75.)**.5);
|
|
if ( vel.length() > 15 )
|
|
{
|
|
let s = Spawn("BallImpact",pos);
|
|
s.angle = atan2(dir.y,dir.x);
|
|
s.pitch = asin(-dir.z);
|
|
}
|
|
}
|
|
if ( slamforce > girth )
|
|
{
|
|
vel *= .8;
|
|
return 1;
|
|
}
|
|
// force bounce
|
|
BlockingMobj = victim;
|
|
A_HandleBounce();
|
|
lasthit = victim;
|
|
// pretend to pass through
|
|
return 1;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("pusher/fly",CHAN_WEAPON,CHANF_LOOPING,.6,3.,2.);
|
|
heat = 1.;
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( InStateSequence(CurState,ResolveState("Death")) )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
heat -= 0.004+0.0004*vel.length();
|
|
A_SoundVolume(CHAN_WEAPON,vel.length()/75.);
|
|
if ( heat <= 0 ) return;
|
|
let s = Spawn("SWWMSmoke",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<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.top.Normal;
|
|
else HitNormal = BlockingFloor.floorplane.Normal;
|
|
}
|
|
else if ( BlockingCeiling )
|
|
{
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
ff = CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) HitNormal = -ff.bottom.Normal;
|
|
else HitNormal = BlockingCeiling.ceilingplane.Normal;
|
|
}
|
|
else if ( BlockingLine )
|
|
{
|
|
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
|
|
HitNormal *= -1;
|
|
}
|
|
else if ( BlockingMobj )
|
|
{
|
|
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
|
|
HitNormal = diff.unit();
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
double bcefact = .9;
|
|
if ( BlockingMobj )
|
|
{
|
|
bcefact *= .7;
|
|
if ( !BlockingMobj.bINVULNERABLE && !BlockingMobj.bNOBLOOD )
|
|
bcefact *= .6;
|
|
}
|
|
vel = bcefact*((vel dot HitNormal)*HitNormal*-1.2+vel);
|
|
// slam jam
|
|
if ( !BlockingMobj )
|
|
{
|
|
A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,max(0.,(vel.length()/60.-.1))**.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 Spreadgun : SWWMWeapon
|
|
{
|
|
bool fired; // shell was used
|
|
Class<Ammo> loadammo, nextammo; // currently loaded shell, next shell to load
|
|
double srecoil;
|
|
|
|
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) && (t.Results.HitType != TRACE_HasHitSky) )
|
|
{
|
|
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.,TRACE_HitSky);
|
|
ProcessTraceHit(sst,origin,dir,0,12000,"SlugImpact",1,true);
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= 0.5;
|
|
}
|
|
for ( int i=0; i<6; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.scale *= .8;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
|
|
}
|
|
for ( int i=0; i<10; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .2;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,6000.);
|
|
A_Recoil(2.);
|
|
invoker.srecoil = -.8;
|
|
break;
|
|
case 2:
|
|
for ( int j=0; j<9; j++ )
|
|
{
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.2);
|
|
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
let p = Spawn("DragonBreathArm",origin);
|
|
p.target = self;
|
|
p.angle = atan2(dir.y,dir.x);
|
|
p.pitch = asin(-dir.z);
|
|
}
|
|
for ( int i=0; i<7; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= .2;
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= .9;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<15; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,2000.);
|
|
A_Recoil(.4);
|
|
invoker.srecoil = -.15;
|
|
break;
|
|
case 3:
|
|
for ( int j=0; j<8; j++ )
|
|
{
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.8);
|
|
let b = Spawn("SaltBeam",level.Vec3Offset(origin,y*cos(a)*s+z*sin(a)*s));
|
|
b.target = self;
|
|
b.angle = atan2(x2.y,x2.x);
|
|
b.pitch = asin(-x2.z);
|
|
}
|
|
for ( int i=0; i<9; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,3,4)*Random[Spreadgun](32,63));
|
|
s.A_SetRenderStyle(.2,STYLE_AddShaded);
|
|
}
|
|
for ( int i=0; i<16; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= .9;
|
|
s.SetShade(Color(1,3,4)*Random[Spreadgun](32,63));
|
|
s.A_SetRenderStyle(.3,STYLE_AddShaded);
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,4000.);
|
|
A_Recoil(1.4);
|
|
invoker.srecoil = -.5;
|
|
break;
|
|
case 4:
|
|
Console.Printf("\cg// TODO Napalm Rounds\c-");
|
|
break;
|
|
case 5:
|
|
a = FRandom[Spreadgun](0,360);
|
|
s = FRandom[Spreadgun](0,.03);
|
|
let b = Spawn("TheBall",origin);
|
|
b.target = self;
|
|
b.angle = atan2(x2.y,x2.x);
|
|
b.pitch = asin(-x2.z);
|
|
b.vel = x2*b.speed;
|
|
for ( int i=0; i<4; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= 0.4;
|
|
}
|
|
for ( int i=0; i<8; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.scale *= .6;
|
|
s.alpha *= .25;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
|
|
}
|
|
for ( int i=0; i<8; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .2;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,2500.);
|
|
A_Recoil(1.);
|
|
invoker.srecoil = -.35;
|
|
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.,TRACE_HitSky);
|
|
ProcessTraceHit(st,origin,dir,6,7000,bc:5);
|
|
}
|
|
for ( int i=0; i<9; i++ )
|
|
{
|
|
let s = Spawn("SWWMViewSmoke",origin);
|
|
SWWMViewSmoke(s).ofs = (15,3,-3);
|
|
s.target = self;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.alpha *= .2;
|
|
}
|
|
for ( int i=0; i<16; i++ )
|
|
{
|
|
let s = Spawn("SWWMSmoke",origin);
|
|
s.special1 = 1;
|
|
s.scale *= .9;
|
|
s.alpha *= .3;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
|
|
}
|
|
for ( int i=0; i<20; i++ )
|
|
{
|
|
let s = Spawn("SWWMSpark",origin);
|
|
s.scale *= .3;
|
|
s.alpha *= .4;
|
|
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,4000.);
|
|
A_Recoil(1.4);
|
|
invoker.srecoil = -.5;
|
|
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]) || (CountInv(invoker.nextammo) <= 0)) )
|
|
{
|
|
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"));
|
|
}
|
|
|
|
override void ModifyDropAmount( int dropamount )
|
|
{
|
|
Super.ModifyDropAmount(dropamount);
|
|
// toss some ammo while we're at it
|
|
A_DropItem(Random[Spreadgun](0,2)?"RedShell":"GreenShell",Random[Spreadgun](1,3));
|
|
}
|
|
|
|
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 RS 1;
|
|
XZW2 T 1 A_Recoil(invoker.srecoil);
|
|
XZW2 U 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;
|
|
}
|
|
}
|