swwmgz_m/zscript/dlc1/swwm_mister.zsc
Marisa the Magician c770276bd7 Various changes related to items:
- All items now have vanilla hitbox sizes for compatibility.
 - Removed extended hitboxes (no longer needed).
 - Blob shadows are now exclusive to players (and eventually monsters).
 - Glows and sparkles are no longer toggleable.
 - Glows and sparkles have fixed size/radius.
 - Item sparkles are now handled by the glows, rather than the player.
2022-10-28 16:49:14 +02:00

1671 lines
49 KiB
Text

// Plutoni Inc. Mortal Rifle (from UnSX 2)
// Slot 9, spawns shared with Candygun
Class MRHitListEntry : HitListEntry
{
// just needs this
bool bExit;
}
// used for splitting model beam between crossed portals (if any)
Class MRRailSeg
{
Vector3 enter, exit;
}
// like the silver bullet tracer except there's no penetration factor
// just a maximum possible travel distance (which is mainly used for probing through walls)
Class MisterRailTracer : LineTracer
{
Actor ignoreme;
Array<MRHitListEntry> hitlist;
Array<Line> shootthroughlist;
Array<WaterHit> waterhitlist;
Array<MRRailSeg> portalseg;
int maxpen;
bool pastwall, fullstop;
Array<WallPenetrate> WallPenetrateList;
Vector3 exitpoint;
transient Array<F3DFloor> ffloors; // needs to be done like this because HAHA SCOPE
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_CrossingPortal )
{
let seg = new("MRRailSeg");
seg.enter = Results.HitPos;
seg.exit = Results.SrcFromTarget;
portalseg.Push(seg);
}
else if ( Results.HitType == TRACE_HitActor )
{
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
if ( Results.HitActor.bSHOOTABLE )
{
let ent = new("MRHitListEntry");
ent.hitactor = Results.HitActor;
ent.hitlocation = Results.HitPos;
ent.x = Results.HitVector;
ent.pastwall = pastwall;
ent.bExit = false;
hitlist.Push(ent);
// also include exit point
Vector3 exit = SWWMUtility.TraceExit(Results.HitActor,Results.HitPos,Results.HitVector);
ent = new("MRHitListEntry");
ent.hitactor = Results.HitActor;
ent.hitlocation = exit;
ent.x = Results.HitVector;
ent.pastwall = pastwall;
ent.bExit = true;
hitlist.Push(ent);
return TRACE_Skip;
}
return TRACE_Skip;
}
else if ( Results.HitType == TRACE_HasHitSky )
{
fullstop = true;
return TRACE_Stop;
}
else if ( Results.HitType != TRACE_HitNone )
{
if ( Results.HitType == TRACE_HitWall )
{
ShootThroughList.Push(Results.HitLine);
if ( (Results.Tier == TIER_Middle) && Results.HitLine.sidedef[1] && !(Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
return TRACE_Skip;
}
for ( int i=1; i<=maxpen; i++ ) // originally meant to trace up to maxdist, but this destroyed performance
{
Vector3 ofs = Results.HitPos+Results.HitVector*i;
if ( level.IsPointInLevel(ofs) )
{
// double-check if we're piercing through a 3D floor (yes this is a thing that happens, oh boy)
Sector s = level.PointInSector(ofs.xy);
bool stop3d = false;
for ( int j=0; j<ffloors.Size(); j++ )
{
if ( ffloors[j].target != s ) continue;
double minz = ffloors[j].bottom.ZAtPoint(ofs.xy);
double maxz = ffloors[j].top.ZAtPoint(ofs.xy);
if ( (ofs.z < minz) || (ofs.z > maxz) ) continue;
stop3d = true;
break;
}
if ( stop3d ) continue;
let wp = new("WallPenetrate");
wp.hittype = Results.HitType;
wp.hitline = Results.HitLine;
wp.hitside = Results.Side;
wp.hittier = Results.Tier;
wp.hitsector = Results.HitSector;
wp.hitffloor = Results.ffloor;
wp.hitpos = Results.HitPos;
wp.hitdir = Results.HitVector;
wp.bustdir = Results.HitVector;
if ( Results.HitType == TRACE_HitWall )
{
wp.hitnormal = (-Results.HitLine.delta.y,Results.HitLine.delta.x,0).unit();
if ( !Results.Side ) wp.hitnormal *= -1;
ShootThroughList.Push(Results.HitLine);
}
else if ( Results.HitType == TRACE_HitCeiling )
wp.hitnormal = Results.HitSector.ceilingplane.Normal;
else if ( Results.HitType == TRACE_HitFloor )
wp.hitnormal = Results.HitSector.floorplane.Normal;
wp.pastwall = pastwall;
WallPenetrateList.Push(wp);
pastwall = true;
// trace backwards to find exit surface
let at = new("AuxiliarySilverBulletTracer");
at.Trace(ofs,level.PointInSector(ofs.xy),-Results.HitVector,2.,0);
let wp2 = new("WallPenetrate");
wp2.hittype = at.Results.HitType;
wp2.hitline = at.Results.HitLine;
wp2.hitside = at.Results.Side;
wp2.hittier = at.Results.Tier;
wp2.hitsector = at.Results.HitSector;
wp2.hitffloor = at.Results.ffloor;
wp2.hitside = at.Results.Side;
wp2.hitpos = at.Results.HitPos;
wp2.hitdir = at.Results.HitVector;
wp2.bustdir = -at.Results.HitVector;
if ( at.Results.HitType == TRACE_HitWall )
{
wp2.hitnormal = (-at.Results.HitLine.delta.y,at.Results.HitLine.delta.x,0).unit();
if ( !at.Results.Side ) wp2.hitnormal *= -1;
if ( at.Results.HitLine.sidedef[1] )
ShootThroughList.Push(at.Results.HitLine);
}
else if ( at.Results.HitType == TRACE_HitCeiling )
wp2.hitnormal = at.Results.HitSector.ceilingplane.Normal;
else if ( at.Results.HitType == TRACE_HitFloor )
wp2.hitnormal = at.Results.HitSector.floorplane.Normal;
else wp2.hitnormal = wp2.hitdir;
wp2.pastwall = pastwall;
WallPenetrateList.Push(wp2);
fullstop = false;
exitpoint = ofs;
return TRACE_Stop;
}
}
fullstop = true;
return TRACE_Stop;
}
return TRACE_Skip;
}
}
Class MisterRailCounter : Thinker
{
PlayerInfo player;
Array<Actor> effectors;
int nkill;
override void Tick()
{
int neff = 0;
for ( int i=0; i<effectors.Size(); i++ )
{
if ( !effectors[i] ) continue;
neff++;
}
if ( neff > 0 ) return;
SWWMUtility.AchievementProgress("rail",nkill,player);
Destroy();
}
}
Class MisterRifle : SWWMWeapon
{
int clipcount;
bool chambered, fired;
bool gchambered, gfired;
bool boltlock;
bool waschambered;
bool wasgchambered;
double prefirecnt;
transient int holdtic;
int firemode;
ui int lastfiremode;
String serialnum;
transient ui SmoothLinearValueInterpolator PreFireInter;
// for alerts in the canvas
transient int lowammotic, noammotic;
Property ClipCount : clipcount;
override void InitializeWeapon()
{
// no round in the chamber
chambered = false;
fired = false;
// no grenade loaded
gchambered = false;
gfired = false;
// bolt isn't locked
boltlock = false;
// generate a serial number
serialnum = "00000";
SWWMUtility.ObscureText(serialnum,Random[MRifle](),true);
}
override void HudTick()
{
Super.HudTick();
if ( !PreFireInter ) PreFireInter = SmoothLinearValueInterpolator.Create(prefirecnt,100.);
PreFireInter.Update(prefirecnt);
if ( lastfiremode && (lastfiremode != firemode+1) && (Owner.player == players[consoleplayer]) )
{
let bar = SWWMStatusBar(statusbar);
if ( bar )
{
bar.ntagstr = StringTable.Localize("$SWWM_MRMODE"..(firemode+1));
bar.ntagtic = level.totaltime;
bar.ntagcol = nametagcolor;
}
}
lastfiremode = firemode+1;
}
override Vector3 GetTraceOffset( int index )
{
return (10,2.8,-2.4);
}
override bool ReportHUDAmmo()
{
return (chambered&&!fired)||(clipcount>0)||(Ammo1.Amount>0)||(Owner.CountInv("MisterRound")>0)||(gchambered&&!gfired)||(Ammo2.Amount>0);
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
if ( sv_infiniteammo || Owner.FindInventory('PowerInfiniteAmmo',true) ) return true;
if ( firemode == PrimaryFire ) return (chambered&&!fired)||(clipcount>0)||(Ammo1.Amount>0)||(Owner.CountInv("MisterRound")>0);
if ( firemode == AltFire ) return (gchambered&&!gfired)||(Ammo2.Amount>0);
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
}
override bool PickupForAmmoSWWM( SWWMWeapon ownedWeapon )
{
bool good = Super.PickupForAmmoSWWM(ownedWeapon);
let Owner = ownedWeapon.Owner;
if ( AmmoGive1 == 0 )
{
// give bullets?
if ( (clipcount > 0) || (chambered && !fired) )
{
Inventory cur = Owner.FindInventory("MisterRound");
if ( !cur )
{
cur = Inventory(Spawn("MisterRound"));
cur.Amount = 0;
cur.AttachToOwner(Owner);
}
int ingun = clipcount+(chambered&&!fired);
int maxgiveamt = min(cur.MaxAmount-cur.Amount,ingun);
int dropamt = ingun-maxgiveamt;
if ( dropamt > 0 ) cur.CreateTossable(dropamt);
cur.Amount = min(cur.MaxAmount,cur.Amount+ingun);
good = true;
}
// give grenade?
if ( gchambered && !gfired )
{
Inventory cur = Owner.FindInventory("MisterGAmmo");
if ( !cur )
{
cur = Inventory(Spawn("MisterGAmmo"));
cur.Amount = 0;
cur.AttachToOwner(Owner);
}
int ingun = (gchambered&&!gfired);
int maxgiveamt = min(cur.MaxAmount-cur.Amount,ingun);
int dropamt = ingun-maxgiveamt;
if ( dropamt > 0 ) cur.CreateTossable(dropamt);
cur.Amount = min(cur.MaxAmount,cur.Amount+ingun);
good = true;
}
}
return good;
}
override void PlayUpSound( Actor origin )
{
if ( (clipcount > 0) && (!chambered || fired) ) origin.A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
else origin.A_StartSound(UpSound,CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_AmmoAlert( bool bOnReload = false )
{
int curammo = invoker.clipcount+(invoker.chambered&&!invoker.fired);
if ( curammo == 0 )
{
// no ammo alert
if ( CheckLocalView() ) A_StartSound("mister/noammo",CHAN_WEAPON,CHANF_OVERLAP);
invoker.noammotic = gametic+50;
}
else if ( (curammo == 4) || (bOnReload && (curammo <= 4)) )
{
// low ammo alert
if ( CheckLocalView() ) A_StartSound("mister/lowammo",CHAN_WEAPON,CHANF_OVERLAP);
invoker.lowammotic = gametic+40;
}
}
action void A_BoltBack( bool bCasing = false )
{
A_StartSound("mister/boltback",CHAN_WEAPON,CHANF_OVERLAP);
if ( bCasing )
{
invoker.waschambered = invoker.chambered;
A_ChangeModel("",1,"","",10,"models","MortalRound_Used.png",CMDL_USESURFACESKIN,-1);
}
invoker.chambered = invoker.fired = false;
A_AmmoAlert();
}
action void A_BoltForward( bool bFlick = false )
{
A_StartSound("mister/boltforward",CHAN_WEAPON,CHANF_OVERLAP);
invoker.boltlock = false;
if ( invoker.ClipCount > 0 )
{
invoker.clipcount--;
invoker.chambered = true;
invoker.fired = false;
}
if ( bFlick && (player == players[consoleplayer]) && swwm_beepboop )
SWWMHandler.AddOneliner("mrflick",2,0);
}
action void A_DropCasing()
{
A_ChangeModel("",1,"","",10,"models","",CMDL_USESURFACESKIN,-1);
if ( !invoker.waschambered ) return;
// brass it up (though it's not made of brass)
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),2*x+3*y-18*z);
let c = Spawn("MisterCasing",origin);
c.angle = angle;
c.pitch = pitch;
c.vel = x*FRandom[Junk](-.25,.25)+y*FRandom[Junk](-.25,.25)-(0,0,FRandom[Junk](4.,6.));
c.vel += vel*.5;
}
action void A_FireSelect()
{
if ( CheckLocalView() ) A_StartSound("mister/fireselect",CHAN_WEAPON,CHANF_OVERLAP);
invoker.firemode = (invoker.firemode+1)%4;
if ( (player == players[consoleplayer]) && swwm_beepboop )
SWWMHandler.AddOneliner("mrtouch",2,0);
}
action void A_MagOut()
{
A_PlayerReload();
A_StartSound("mister/magout",CHAN_WEAPON,CHANF_OVERLAP);
A_ChangeModel("",1,"","",7,"models",(invoker.clipcount>0)?"MortalMag.png":"MortalMag_Empty.png",CMDL_USESURFACESKIN,-1);
MagAmmo ma = MagAmmo(FindInventory("MisterRound"));
if ( !ma )
{
ma = MagAmmo(Spawn("MisterRound"));
ma.Amount = 0;
ma.AttachToOwner(self);
}
int maxgiveamt = min(ma.MaxAmount-ma.Amount,invoker.clipcount);
int dropamt = invoker.clipcount-maxgiveamt;
if ( dropamt > 0 ) invoker.BufferMagAmmo("MisterRound",dropamt);
ma.Amount = min(ma.MaxAmount,ma.Amount+invoker.clipcount);
ma.MagFill();
invoker.clipcount = 0;
}
action void A_MagDrop()
{
// ensure mag is invisible to avoid weird interpolation to hand
A_ChangeModel("",1,"","",7,"models","",CMDL_USESURFACESKIN,-1);
if ( swwm_nomagdrop ) return;
// drop it
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),6*x+3*y-18*z);
let c = Spawn("MisterMag",origin);
c.angle = angle;
c.pitch = pitch;
c.vel = x*FRandom[Junk](-.2,.2)+y*FRandom[Junk](-.2,.2)-(0,0,FRandom[Junk](2.,4.));
c.vel += vel*.5;
}
action void A_MagGrab()
{
// ensure mag is full
A_ChangeModel("",1,"","",7,"models","MortalMag.png",CMDL_USESURFACESKIN,-1);
A_StartSound("mister/magget",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_MagIn()
{
MagAmmo sb = MagAmmo(FindInventory("MisterRound"));
if ( sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) )
invoker.clipcount = invoker.default.clipcount;
else if ( (invoker.Ammo1.Amount <= 0) || (sb.Amount >= sb.ClipSize) )
{
int takeamt = min(sb.Amount,sb.ClipSize);
invoker.clipcount = takeamt;
sb.Amount -= takeamt;
int req = invoker.default.ClipCount-invoker.clipcount;
if ( req > 0 ) invoker.clipcount += invoker.FetchBufferedMagAmmo("MisterRound",req);
}
else if ( invoker.FetchBufferedMagAmmo("MisterRound",sb.ClipSize,true) )
invoker.clipcount = invoker.default.clipcount;
else
{
invoker.Ammo1.Amount = max(0,invoker.Ammo1.Amount-1);
invoker.clipcount = invoker.default.clipcount;
}
invoker.ClearBufferedAmmo();
A_AmmoAlert(true);
}
action State A_MisterFire()
{
if ( !invoker.chambered || invoker.fired )
{
// auto-chamber just in case (rarely ever happens)
if ( invoker.ClipCount > 0 )
return ResolveState("AutoCycle");
// auto-reload if we're out
if ( (invoker.clipcount <= 0) && ((invoker.Ammo1.Amount > 0) || (CountInv("MisterRound") > 0) || sv_infiniteammo || FindInventory("PowerInfiniteAmmo")) )
return ResolveState("Reload");
}
// this one is handled separately
if ( invoker.firemode >= 3 ) return ResolveState("FireOverpressure");
A_PlayerFire();
A_SWWMFlash();
invoker.fired = true;
if ( invoker.firemode == 2 )
{
// stream shot
// individual sub-shots handled in the state sequence, not here
A_StartSound("mister/firestream",CHAN_WEAPON,CHANF_OVERLAP);
SWWMHandler.DoFlash(self,Color(32,64,224,255),2);
A_AlertMonsters(swwm_uncapalert?0:4000);
A_MisterFireStream(0);
return ResolveState("FireStream");
}
SWWMHandler.DoFlash(self,Color(48,64,224,255),3);
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.8*y-2.4*z);
if ( invoker.firemode == 1 )
{
// cluster shot
A_StartSound("mister/firescatter",CHAN_WEAPON,CHANF_OVERLAP);
SWWMUtility.DoKnockback(self,-x,15000.);
A_QuakeEx(6,6,6,8,0,10,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.3);
A_BumpFOV(.92);
A_AlertMonsters(swwm_uncapalert?0:8000);
for ( int i=0; i<10; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .6;
s.alpha *= .15;
s.speed *= .8;
s.vel += vel*.5+x*FRandom[Mister](1.,4.);
s.SetShade(Color(0,3,4)*Random[ExploS](48,63));
}
Vector3 x2, y2, z2;
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
double a, s;
Vector3 dir;
for ( int i=0; i<8; i++ )
{
// initial shot, triangle
if ( i < 3 )
{
a = 90+120*i+FRandom[Mister](-15,15);
s = .04+FRandom[Mister](-.01,.01);
dir = SWWMUtility.ConeSpread(x2,y2*1.5,z2,a,s);
}
// next five shots, pentagon
else
{
a = -90+72*(i-3)+FRandom[Mister](-9,9);
s = .08+FRandom[Mister](-.02,.02);
dir = SWWMUtility.ConeSpread(x2,y2*2.,z2,a,s);
}
FLineTraceData d;
LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION|TRF_NOSKY,origin.z,origin.x,origin.y,d);
SWWMBulletTrail.DoTrail(self,origin,dir,10000,6);
if ( d.HitType == TRACE_HitActor )
{
if ( d.HitActor.bNOBLOOD || d.HitActor.bDORMANT )
{
let p = Spawn("SpreadImpact",d.HitLocation);
p.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
p.pitch = asin(d.HitDir.z);
p.target = self;
}
let b = Spawn("MisterBuckshotImpact",d.HitLocation-d.HitDir*4.);
b.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
b.pitch = asin(d.HitDir.z);
b.target = self;
MisterBulletImpact(b).A_BulletExplode();
}
else if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = -d.HitDir;
if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal;
else hitnormal = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal;
else hitnormal = d.HitSector.ceilingplane.Normal;
}
else if ( d.HitType == TRACE_HitWall )
{
hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) hitnormal *= -1;
}
let p = Spawn("SpreadImpact",d.HitLocation+hitnormal*0.01);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
p.target = self;
if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation);
let b = Spawn("MisterBuckshotImpact",d.HitLocation+hitnormal*4.);
b.angle = atan2(hitnormal.y,hitnormal.x);
b.pitch = asin(-hitnormal.z);
b.target = self;
MisterBulletImpact(b).A_BulletExplode();
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,444,self,d.HitDir,d.HitLocation.z);
}
}
return ResolveState("FireCluster");
}
// precision shot
A_StartSound("mister/firesemi",CHAN_WEAPON,CHANF_OVERLAP);
SWWMUtility.DoKnockback(self,-x,9000.);
A_QuakeEx(5,5,5,6,0,10,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.8);
A_BumpFOV(.95);
A_AlertMonsters(swwm_uncapalert?0:6000);
for ( int i=0; i<6; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .4;
s.alpha *= .1;
s.speed *= .5;
s.vel += vel*.5+x*FRandom[Mister](1.,2.);
s.SetShade(Color(0,3,4)*Random[ExploS](48,63));
}
Vector3 dir;
dir = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
FLineTraceData d;
LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION|TRF_NOSKY,origin.z,origin.x,origin.y,d);
SWWMBulletTrail.DoTrail(self,origin,dir,10000,2);
if ( d.HitType == TRACE_HitActor )
{
if ( d.HitActor.bNOBLOOD || d.HitActor.bDORMANT )
{
let p = Spawn("SWWMBulletImpact",d.HitLocation);
p.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
p.pitch = asin(d.HitDir.z);
p.target = self;
}
let b = Spawn("MisterBulletImpact",d.HitLocation-d.HitDir*4.);
b.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
b.pitch = asin(d.HitDir.z);
b.target = self;
MisterBulletImpact(b).A_BulletExplode();
}
else if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = -d.HitDir;
if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal;
else hitnormal = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal;
else hitnormal = d.HitSector.ceilingplane.Normal;
}
else if ( d.HitType == TRACE_HitWall )
{
hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) hitnormal *= -1;
}
let p = Spawn("SWWMBulletImpact",d.HitLocation+hitnormal*0.01);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
p.target = self;
if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation);
let b = Spawn("MisterBulletImpact",d.HitLocation+hitnormal*4.);
b.angle = atan2(hitnormal.y,hitnormal.x);
b.pitch = asin(-hitnormal.z);
b.target = self;
MisterBulletImpact(b).A_BulletExplode();
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,444,self,d.HitDir,d.HitLocation.z);
}
return ResolveState(null);
}
action void A_MisterFireStream( int index )
{
if ( index != 0 )
{
A_PlayerFire();
SWWMHandler.DoFlash(self,Color(32,64,224,255),2);
A_SWWMFlash("FastFlash");
}
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.8*y-2.4*z);
SWWMUtility.DoKnockback(self,-x,5000.);
A_QuakeEx(3,3,3,5,0,10,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.4);
A_BumpFOV(.97);
if ( index == 0 ) A_AlertMonsters(swwm_uncapalert?0:5000);
for ( int i=0; i<3; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .4;
s.alpha *= .1;
s.speed *= .5;
s.vel += vel*.5+x*FRandom[Mister](1.,2.);
s.SetShade(Color(0,3,4)*Random[ExploS](48,63));
}
Vector3 x2, y2, z2;
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
static const double spd[] = {.00,.01,.03,.06,.10};
double a = FRandom[Mister](0,360), s = FRandom[Mister](spd[index],spd[index+1]);
Vector3 dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
FLineTraceData d;
LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION|TRF_NOSKY,origin.z,origin.x,origin.y,d);
SWWMBulletTrail.DoTrail(self,origin,dir,10000,2);
if ( d.HitType == TRACE_HitActor )
{
if ( d.HitActor.bNOBLOOD || d.HitActor.bDORMANT )
{
let p = Spawn("SWWMBulletImpact",d.HitLocation);
p.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
p.pitch = asin(d.HitDir.z);
p.target = self;
}
let b = Spawn("MisterStreamImpact",d.HitLocation-d.HitDir*4.);
b.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
b.pitch = asin(d.HitDir.z);
b.target = self;
MisterBulletImpact(b).A_BulletExplode();
}
else if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = -d.HitDir;
if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal;
else hitnormal = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal;
else hitnormal = d.HitSector.ceilingplane.Normal;
}
else if ( d.HitType == TRACE_HitWall )
{
hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) hitnormal *= -1;
}
let p = Spawn("SWWMBulletImpact",d.HitLocation+hitnormal*0.01);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
p.target = self;
if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation);
let b = Spawn("MisterStreamImpact",d.HitLocation+hitnormal*4.);
b.angle = atan2(hitnormal.y,hitnormal.x);
b.pitch = asin(-hitnormal.z);
b.target = self;
MisterBulletImpact(b).A_BulletExplode();
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,444,self,d.HitDir,d.HitLocation.z);
}
}
override void OwnerDied()
{
Super.OwnerDied();
prefirecnt = 0;
}
override void Travelled()
{
Super.Travelled();
prefirecnt = 0;
}
action void A_MisterStartRail()
{
// pre-heat our ovens
A_StartSound("mister/chargeover",CHAN_WEAPON,CHANF_OVERLAP);
A_StartSound("mister/holdover",CHAN_WEAPONEXTRA,CHANF_LOOPING,.01,4.,.5);
invoker.prefirecnt = 0;
}
action State A_MisterHoldRail()
{
if ( (invoker.prefirecnt < 100) && !(player.cmd.buttons&BT_ATTACK) )
{
invoker.prefirecnt = 0;
A_StopSound(CHAN_WEAPONEXTRA);
A_StartSound("mister/cancelover",CHAN_WEAPON,CHANF_OVERLAP);
return ResolveState("FireOverpressureCancel");
}
if ( invoker.prefirecnt < 100 )
{
invoker.prefirecnt += 1.5;
if ( invoker.prefirecnt >= 100 )
{
invoker.prefirecnt = 100;
invoker.holdtic = gametic;
}
}
A_SoundVolume(CHAN_WEAPONEXTRA,clamp(invoker.prefirecnt*.01,.01,1.));
A_SoundPitch(CHAN_WEAPONEXTRA,clamp(.5+invoker.prefirecnt*.005,.5,1.)**.5);
if ( (invoker.prefirecnt >= 100) && !((gametic-invoker.holdtic)%32) && CheckLocalView() )
A_StartSound("mister/beepover",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf((invoker.prefirecnt>=100)&&!(player.cmd.buttons&BT_ATTACK),"FireOverpressureRelease");
}
action void A_MisterFireRail()
{
invoker.prefirecnt = 0;
A_StopSound(CHAN_WEAPONEXTRA);
A_PlayerFire();
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.8*y-2.4*z);
SWWMHandler.DoFlash(self,Color(64,64,224,255),9);
A_SWWMFlash();
invoker.fired = true;
A_StartSound("mister/fireover",CHAN_WEAPON,CHANF_OVERLAP);
SWWMUtility.DoKnockback(self,-x,90000.);
A_QuakeEx(8,8,8,12,0,10,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:2.);
A_BumpFOV(.85);
A_AlertMonsters(swwm_uncapalert?0:12000);
for ( int i=0; i<12; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .4;
s.alpha *= .1;
s.vel += vel*.5+x*FRandom[Mister](1.,12.);
s.SetShade(Color(0,3,4)*Random[ExploS](48,63));
}
Vector3 dir, startdir;
startdir = dir = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
let mrt = new("MisterRailTracer"); // I pity the fool
mrt.ignoreme = self;
mrt.hitlist.Clear();
mrt.shootthroughlist.Clear();
mrt.waterhitlist.Clear();
mrt.wallpenetratelist.Clear();
mrt.ffloors.Clear();
mrt.portalseg.Clear();
mrt.maxpen = 400;
for ( int i=0; i<level.Sectors.Size(); i++ )
{
Sector s = level.Sectors[i];
for ( int j=0; j<s.Get3DFloorCount(); j++ )
{
F3DFloor ff = s.Get3DFloor(j);
if ( ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID) )
mrt.ffloors.Push(ff);
}
}
mrt.pastwall = false;
Vector3 norigin = origin;
double maxdist = 10000.;
do
{
mrt.fullstop = true;
mrt.Trace(norigin,level.PointInSector(norigin.xy),dir,maxdist,TRACE_HitSky|TRACE_ReportPortals);
mrt.maxpen -= 100;
maxdist -= (mrt.exitpoint-norigin).length();
norigin = mrt.exitpoint;
dir = mrt.Results.HitVector;
}
while ( !mrt.fullstop );
let mrc = new("MisterRailCounter");
mrc.ChangeStatNum(STAT_USER); // so it can tick
mrc.player = player;
Vector3 sstart = origin;
// beam segments
if ( mrt.portalseg.Size() <= 0 )
{
Vector3 sdir = mrt.Results.HitPos-sstart;
double sdist = sdir.length();
sdir /= sdist;
Actor b;
if ( sdist > 32 )
{
b = Spawn("MisterRailBeam",sstart);
b.target = self;
b.frame = 1;
b.scale.y = 32;
b.angle = atan2(sdir.y,sdir.x);
b.pitch = asin(-sdir.z)+90;
MisterRailBeam(b).mrc = mrc;
mrc.effectors.Push(b);
sstart += sdir*32;
sdist -= 32;
b = Spawn("MisterRailBeam",sstart);
}
else
{
b = Spawn("MisterRailBeam",sstart);
b.frame = 1;
}
b.target = self;
b.scale.y = sdist;
b.angle = atan2(sdir.y,sdir.x);
b.pitch = asin(-sdir.z)+90;
MisterRailBeam(b).mrc = mrc;
mrc.effectors.Push(b);
}
else for ( int i=0; i<mrt.portalseg.Size(); i++ )
{
Vector3 sdir = mrt.portalseg[i].enter-sstart;
double sdist = sdir.length();
sdir /= sdist;
Actor b;
if ( i == 0 )
{
if ( sdist > 32 )
{
b = Spawn("MisterRailBeam",sstart);
b.target = self;
b.frame = 1;
b.scale.y = 32;
b.angle = atan2(sdir.y,sdir.x);
b.pitch = asin(-sdir.z)+90;
MisterRailBeam(b).mrc = mrc;
mrc.effectors.Push(b);
sstart += sdir*32;
sdist -= 32;
b = Spawn("MisterRailBeam",sstart);
}
else
{
b = Spawn("MisterRailBeam",sstart);
b.frame = 1;
}
}
else b = Spawn("MisterRailBeam",sstart);
b.target = self;
b.scale.y = sdist;
b.angle = atan2(sdir.y,sdir.x);
b.pitch = asin(-sdir.z)+90;
MisterRailBeam(b).mrc = mrc;
mrc.effectors.Push(b);
sstart = mrt.portalseg[i].exit;
if ( i == (mrt.portalseg.Size()-1) )
{
sdir = mrt.Results.HitPos-sstart;
sdist = sdir.length();
sdir /= sdist;
b = Spawn("MisterRailBeam",sstart);
b.target = self;
b.scale.y = sdist;
b.angle = atan2(sdir.y,sdir.x);
b.pitch = asin(-sdir.z)+90;
MisterRailBeam(b).mrc = mrc;
mrc.effectors.Push(b);
}
}
for ( int i=0; i<mrt.ShootThroughList.Size(); i++ )
{
mrt.ShootThroughList[i].Activate(self,0,SPAC_Impact);
mrt.ShootThroughList[i].Activate(self,0,SPAC_PCross);
}
for ( int i=0; i<mrt.WaterHitList.Size(); i++ )
{
let b = Spawn("InvisibleSplasher",mrt.WaterHitList[i].hitpos);
b.target = self;
b.A_CheckTerrain();
}
Array<MisterBulletImpact> bi; // so we can ignite them all at once after main contact damage
bool bskipme = false;
for ( int i=0; i<mrt.hitlist.Size(); i++ )
{
if ( mrt.HitList[i].HitActor && !mrt.HitList[i].bExit )
{
SWWMUtility.DoKnockback(mrt.HitList[i].HitActor,mrt.HitList[i].x+(0,0,0.025),80000);
let p = SWWMPuff.Setup(mrt.HitList[i].HitLocation,mrt.HitList[i].x,invoker,self,mrt.HitList[i].HitActor);
mrt.HitList[i].HitActor.DamageMobj(p,self,4444,'Mortal',DMG_FOILINVUL|DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF);
if ( !mrt.HitList[i].HitActor || (mrt.HitList[i].HitActor.Health <= 0) )
mrc.nkill++;
}
if ( mrt.HitList[i].bExit && (i < mrt.hitlist.Size()-1)
&& (level.Vec3Diff(mrt.HitList[i].HitLocation,mrt.HitList[i+1].HitLocation).length() < 100) )
{
bskipme = true;
continue;
}
if ( bskipme )
{
bskipme = false;
continue;
}
if ( mrt.HitList[i].bExit )
{
let b = MisterBulletImpact(Spawn("MisterRailExitImpact",mrt.hitlist[i].HitLocation-mrt.hitlist[i].x*4.));
b.angle = atan2(mrt.HitList[i].x.y,mrt.HitList[i].x.x);
b.pitch = asin(-mrt.HitList[i].x.z);
b.target = self;
b.mrc = mrc;
mrc.effectors.Push(b);
bi.Push(b);
continue;
}
let b = MisterBulletImpact(Spawn("MisterRailEntryImpact",mrt.hitlist[i].HitLocation+mrt.hitlist[i].x*4.));
b.angle = atan2(mrt.HitList[i].x.y,mrt.HitList[i].x.x)+180;
b.pitch = asin(mrt.HitList[i].x.z);
b.target = self;
b.mrc = mrc;
mrc.effectors.Push(b);
bi.Push(b);
}
LineTracer faketracer = new("LineTracer");
for ( int i=0; i<mrt.wallpenetratelist.Size(); i++ )
{
if ( mrt.WallPenetrateList[i].hittype == TRACE_HitWall )
mrt.WallPenetrateList[i].hitline.Activate(self,mrt.WallPenetrateList[i].hitside,SPAC_Impact);
if ( swwm_omnibust )
{
faketracer.Results.HitType = mrt.WallPenetrateList[i].HitType;
faketracer.Results.HitSector = mrt.WallPenetrateList[i].HitSector;
faketracer.Results.HitLine = mrt.WallPenetrateList[i].HitLine;
faketracer.Results.ffloor = mrt.WallPenetrateList[i].HitFFloor;
faketracer.Results.Side = mrt.WallPenetrateList[i].HitSide;
faketracer.Results.Tier = mrt.WallPenetrateList[i].HitTier;
BusterWall.Bust(faketracer.Results,444,self,mrt.WallPenetrateList[i].BustDir,mrt.WallPenetrateList[i].HitPos.z);
}
bool bExit = !!(i%2); // odd-numbered entries are exit points
if ( bExit && (i < mrt.wallpenetratelist.Size()-1)
&& (level.Vec3Diff(mrt.wallpenetratelist[i].HitPos,mrt.wallpenetratelist[i+1].HitPos).length() < 100) )
{
bskipme = true;
continue;
}
if ( bskipme )
{
bskipme = false;
continue;
}
Vector3 hitpos = mrt.WallPenetrateList[i].hitpos;
Vector3 hitnormal = mrt.WallPenetrateList[i].hitnormal;
let b = MisterBulletImpact(Spawn(bExit?"MisterRailExitImpact":"MisterRailEntryImpact",hitpos+hitnormal*4.));
b.angle = atan2(hitnormal.y,hitnormal.x);
b.pitch = asin(-hitnormal.z);
b.target = self;
b.mrc = mrc;
mrc.effectors.Push(b);
bi.Push(b);
}
if ( (mrt.Results.HitType != TRACE_HitNone) && (mrt.Results.HitType != TRACE_HasHitSky) && (mrt.Results.HitType != TRACE_HitActor) )
{
Vector3 hitnormal = -mrt.Results.HitVector;
if ( mrt.Results.HitType == TRACE_HitFloor )
{
if ( mrt.Results.FFloor ) hitnormal = -mrt.Results.FFloor.top.Normal;
else hitnormal = mrt.Results.HitSector.floorplane.Normal;
}
else if ( mrt.Results.HitType == TRACE_HitCeiling )
{
if ( mrt.Results.FFloor ) hitnormal = -mrt.Results.FFloor.bottom.Normal;
else hitnormal = mrt.Results.HitSector.ceilingplane.Normal;
}
else if ( mrt.Results.HitType == TRACE_HitWall )
{
hitnormal = (-mrt.Results.HitLine.delta.y,mrt.Results.HitLine.delta.x,0).unit();
if ( !mrt.Results.Side ) hitnormal *= -1;
}
let b = MisterBulletImpact(Spawn("MisterRailEntryImpact",mrt.Results.HitPos+hitnormal*4.));
b.angle = atan2(hitnormal.y,hitnormal.x);
b.pitch = asin(-hitnormal.z);
b.target = self;
b.mrc = mrc;
mrc.effectors.Push(b);
bi.Push(b);
if ( mrt.Results.HitType == TRACE_HitWall ) mrt.Results.HitLine.RemoteActivate(self,mrt.Results.Side,SPAC_Impact,mrt.Results.HitPos);
if ( swwm_omnibust ) BusterWall.Bust(mrt.Results,444,self,mrt.Results.HitVector,mrt.Results.HitPos.z);
}
for ( int i=0; i<bi.Size(); i++ ) bi[i].A_BulletExplode();
double dist = level.Vec3Diff(origin,mrt.Results.HitPos).length();
for ( double d=0.; d<=dist; d+=250. )
{
Vector3 p = level.Vec3Offset(origin,startdir*d);
if ( !level.IsPointInlevel(p) ) continue;
Spawn("MisterRailLight",p);
}
if ( mrt.wallpenetratelist.Size() > 0 )
{
Vector3 start = origin;
Vector3 end = mrt.WallPenetrateList[0].hitpos;
Vector3 tdir = level.Vec3Diff(start,end);
double dist = tdir.length();
tdir /= dist;
MisterRailHit mrh;
for ( double d=50.; d<=dist; d+=100. )
{
Vector3 ofs = level.Vec3Offset(start,tdir*d);
if ( !level.IsPointInLevel(ofs) ) continue;
if ( mrh )
{
mrh.SetOrigin(ofs,false);
mrh.Explode();
continue;
}
mrh = MisterRailHit(Spawn("MisterRailHit",ofs));
mrh.target = self;
mrh.mrc = mrc;
mrc.effectors.Push(mrh);
mrh.Explode();
}
for ( double d=4.; d<=dist; d+=16. )
{
if ( !Random[ExploS](0,1) ) continue;
Vector3 ofs = level.Vec3Offset(start,tdir*d);
if ( !level.IsPointInLevel(ofs) ) continue;
let b = Spawn("SWWMHalfSmoke",ofs);
b.Scale *= FRandom[ExploS](.7,1.4);
b.alpha *= .2;
b.special1 = Random[ExploS](1,3);
b.SetShade(Color(2,3,4)*Random[ExploS](48,63));
}
for ( int i=1; i<mrt.WallPenetrateList.Size(); i+=2 )
{
start = mrt.WallPenetrateList[i].hitpos;
if ( i >= mrt.WallPenetrateList.Size()-1 ) end = mrt.Results.HitPos;
else end = mrt.WallPenetrateList[i+1].hitpos;
tdir = level.Vec3Diff(start,end);
dist = tdir.length();
tdir /= dist;
for ( double d=50.; d<=dist; d+=100. )
{
Vector3 ofs = level.Vec3Offset(start,tdir*d);
if ( !level.IsPointInLevel(ofs) ) continue;
if ( mrh )
{
mrh.SetOrigin(ofs,false);
mrh.Explode();
continue;
}
mrh = MisterRailHit(Spawn("MisterRailHit",ofs));
mrh.target = self;
mrh.mrc = mrc;
mrc.effectors.Push(mrh);
mrh.Explode();
}
for ( double d=4.; d<=dist; d+=16. )
{
if ( !Random[ExploS](0,1) ) continue;
Vector3 ofs = level.Vec3Offset(start,tdir*d);
if ( !level.IsPointInLevel(ofs) ) continue;
let b = Spawn("SWWMHalfSmoke",ofs);
b.Scale *= FRandom[ExploS](.7,1.4);
b.alpha *= .2;
b.special1 = Random[ExploS](1,3);
b.SetShade(Color(2,3,4)*Random[ExploS](48,63));
}
}
}
else
{
MisterRailHit mrh;
for ( double d=50.; d<=mrt.Results.Distance; d+=100. )
{
Vector3 ofs = level.Vec3Offset(origin,startdir*d);
if ( !level.IsPointInLevel(ofs) ) continue;
if ( mrh )
{
mrh.SetOrigin(ofs,false);
mrh.Explode();
continue;
}
mrh = MisterRailHit(Spawn("MisterRailHit",ofs));
mrh.target = self;
mrh.mrc = mrc;
mrc.effectors.Push(mrh);
mrh.Explode();
}
for ( double d=4.; d<=mrt.Results.Distance; d+=16. )
{
if ( !Random[ExploS](0,1) ) continue;
Vector3 ofs = level.Vec3Offset(origin,startdir*d);
if ( !level.IsPointInLevel(ofs) ) continue;
let b = Spawn("SWWMHalfSmoke",ofs);
b.Scale *= FRandom[ExploS](.7,1.4);
b.alpha *= .2;
b.special1 = Random[ExploS](1,3);
b.SetShade(Color(2,3,4)*Random[ExploS](48,63));
}
}
}
action void A_MisterFireGrenade()
{
A_PlayerFire();
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.8*y-4.*z);
SWWMHandler.DoFlash(self,Color(64,64,224,255),3);
A_SWWMFlash("AltFlash");
invoker.gfired = true;
A_StartSound("mister/grenade",CHAN_WEAPON,CHANF_OVERLAP);
SWWMUtility.DoKnockback(self,-x,12000.);
A_QuakeEx(4,4,4,5,0,10,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.6);
A_BumpFOV(.96);
A_AlertMonsters(swwm_uncapalert?0:5000);
for ( int i=0; i<9; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .4;
s.alpha *= .1;
s.vel += vel*.5+x*FRandom[Mister](1.,4.);
s.SetShade(Color(0,3,4)*Random[ExploS](48,63));
}
Vector3 dir;
dir = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
let p = Spawn("MisterGrenade",origin);
p.target = self;
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = dir*p.speed;
}
action void A_GrenadeOpen()
{
A_PlayerReload();
A_StartSound("mister/grenadeopen");
invoker.wasgchambered = invoker.gchambered;
// theoretically, this is an either-or situation
// either there's no grenade in the chamber whatsoever
// or there's the casing of a fired grenade
if ( !invoker.wasgchambered )
{
// hide both surfaces
A_ChangeModel("",1,"","",8,"models","",CMDL_USESURFACESKIN,-1);
A_ChangeModel("",1,"","",9,"models","",CMDL_USESURFACESKIN,-1);
}
else
{
// show only a fired cartridge
A_ChangeModel("",1,"","",8,"models","MortalGrenade_Used.png",CMDL_USESURFACESKIN,-1);
A_ChangeModel("",1,"","",9,"models","",CMDL_USESURFACESKIN,-1);
}
invoker.gfired = invoker.gchambered = false;
}
action void A_GrenadeDrop()
{
A_ChangeModel("",1,"","",8,"models","",CMDL_USESURFACESKIN,-1);
A_ChangeModel("",1,"","",9,"models","",CMDL_USESURFACESKIN,-1);
if ( !invoker.wasgchambered ) return;
// droppage
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),8*x+3*y-18*z);
let c = Spawn("MisterGCasing",origin);
c.angle = angle;
c.pitch = pitch;
c.vel = x*FRandom[Junk](-.2,.2)+y*FRandom[Junk](-.2,.2)-(0,0,FRandom[Junk](2.,4.));
c.vel += vel*.5;
}
action void A_GrenadeGrab()
{
// the full thingy
A_ChangeModel("",1,"","",8,"models","MortalGrenade.png",CMDL_USESURFACESKIN,-1);
A_ChangeModel("",1,"","",9,"models","MortalGrenade.png",CMDL_USESURFACESKIN,-1);
A_StartSound("mister/grenadeget",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_GrenadeIn()
{
A_StartSound("mister/grenadein",CHAN_WEAPON,CHANF_OVERLAP);
if ( !sv_infiniteammo && !FindInventory("PowerInfiniteAmmo") )
invoker.Ammo2.Amount = max(0,invoker.Ammo2.Amount-1);
invoker.gchambered = true;
invoker.gfired = false;
}
action void A_GrenadeClose()
{
// we no longer need the model, hide it
A_ChangeModel("",1,"","",8,"models","",CMDL_USESURFACESKIN,-1);
A_ChangeModel("",1,"","",9,"models","",CMDL_USESURFACESKIN,-1);
A_StartSound("mister/grenadeclose",CHAN_WEAPON,CHANF_OVERLAP);
}
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkSound("mister/select");
MarkSound("mister/deselect");
MarkSound("mister/meleestart");
MarkSound("mister/meleeend");
MarkSound("mister/boltback");
MarkSound("mister/boltforward");
MarkSound("mister/fireselect");
MarkSound("mister/lowammo");
MarkSound("mister/noammo");
MarkSound("mister/firesemi");
MarkSound("mister/firescatter");
MarkSound("mister/firestream");
MarkSound("mister/fireover");
MarkSound("mister/chargeover");
MarkSound("mister/magout");
MarkSound("mister/magin");
MarkSound("mister/grenade");
MarkSound("mister/grenadeopen");
MarkSound("mister/grenadeget");
MarkSound("mister/grenadein");
MarkSound("mister/grenadeclose");
MarkSound("mister/hitsemi1");
MarkSound("mister/hitsemi2");
MarkSound("mister/hitsemi3");
MarkSound("mister/hitscatter1");
MarkSound("mister/hitscatter2");
MarkSound("mister/hitscatter3");
MarkSound("mister/hitstream1");
MarkSound("mister/hitstream2");
MarkSound("mister/hitstream3");
MarkSound("mister/hitover1");
MarkSound("mister/hitover2");
MarkSound("mister/hitover3");
MarkSound("mister/hitgrenade1");
MarkSound("mister/hitgrenade2");
MarkSound("mister/hitgrenade3");
MarkSound("mister/hitgrenadesub1");
MarkSound("mister/hitgrenadesub2");
MarkSound("mister/hitgrenadesub3");
MarkSound("mister/casing1");
MarkSound("mister/casing2");
MarkSound("mister/casing3");
MarkSound("mister/casing4");
MarkSound("mister/gcasing1");
MarkSound("mister/gcasing2");
MarkSound("mister/gcasing3");
MarkSound("mister/gcasing4");
MarkSound("mister/gbounce1");
MarkSound("mister/gbounce2");
MarkSound("mister/gbounce3");
MarkSound("mister/gbouncesub1");
MarkSound("mister/gbouncesub2");
MarkSound("mister/gbouncesub3");
}
Default
{
Tag "$T_MORTALRIFLE";
Inventory.PickupMessage "$T_MORTALRIFLE";
Obituary "$O_MORTALRIFLE";
SWWMWeapon.Tooltip "$TT_MORTALRIFLE";
SWWMWeapon.GetLine "getmortalrifle";
Weapon.SlotNumber 9;
Weapon.SlotPriority 2.;
Weapon.SelectionOrder 850;
Weapon.UpSound "mister/select";
Weapon.AmmoType1 "MisterAmmo";
Weapon.AmmoGive1 1;
Weapon.AmmoType2 "MisterGAmmo";
Weapon.AmmoGive2 0;
SWWMWeapon.DropAmmoType "SWWMCellAmmoBig";
MisterRifle.ClipCount 12;
Stamina 1600000;
+SWWMWEAPON.NOFIRSTGIVE;
+SWWMWEAPON.HASSCRTEX;
+WEAPON.BFG;
}
States
{
Select:
XZW2 P 2
{
A_ChangeModel("",1,"","",7,"models","MortalMag.png",CMDL_USESURFACESKIN,-1);
A_FullRaise();
if ( invoker.boltlock ) return ResolveState("SelectLock");
if ( (invoker.clipcount > 0) && (!invoker.chambered || invoker.fired) )
return ResolveState("SelectCycle");
return ResolveState(null);
}
XZW2 QRSTUV 2;
XZW2 WXYZ 1;
XZW3 ABCD 2;
Goto Ready;
SelectLock:
XZWB TUVWXYZ 2;
XZWC ABCD 1;
XZWC EFGH 2;
Goto ReadyLock;
SelectCycle:
XZWI RSTUVWXYZ 2;
XZWJ A 2 A_BoltBack();
XZWJ B 2;
XZWJ C 4;
XZWJ D 2
{
A_BoltForward();
A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.15);
}
XZWJ EFG 2;
XZWJ HIJK 1;
XZWJ LMNO 2;
Goto Ready;
Ready:
XZW2 A 1
{
int flg = (WRF_ALLOWRELOAD|WRF_ALLOWZOOM|WRF_ALLOWUSER1);
// can't alt-fire if we have no grenades left
if ( (!invoker.gchambered || invoker.gfired) && (invoker.Ammo2.Amount <= 0) && !sv_infiniteammo && !FindInventory("PowerInfiniteAmmo") )
flg |= WRF_NOSECONDARY;
A_WeaponReady(flg);
if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK) )
invoker.CheckAmmo(EitherFire,true);
}
Wait;
ReadyLock:
XZWB E 1
{
int flg = (WRF_ALLOWRELOAD|WRF_ALLOWZOOM|WRF_ALLOWUSER1);
// can't alt-fire if we have no grenades left
if ( (!invoker.gchambered || invoker.gfired) && (invoker.Ammo2.Amount <= 0) && !sv_infiniteammo && !FindInventory("PowerInfiniteAmmo") )
flg |= WRF_NOSECONDARY;
// can't use primary fire if we have no ammo to reload from
if ( (invoker.Ammo1.Amount <= 0) && (CountInv("MisterRound") <= 0) && !sv_infiniteammo && !FindInventory("PowerInfiniteAmmo") )
flg |= WRF_NOPRIMARY;
A_WeaponReady(flg);
if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK) )
invoker.CheckAmmo(EitherFire,true);
}
Wait;
AutoCycle:
XZW2 A 1 A_JumpIf(invoker.clipcount<=0,"AutoCycleLock");
XZW6 H 2 A_BoltBack(true);
XZW6 IJ 2;
XZW6 K 2 A_BoltForward();
XZW6 LMN 2;
XZW6 O 3 A_DropCasing();
Goto Ready;
AutoCycleLock:
XZW2 A 1 { invoker.boltlock = true; }
XZWA W 2 A_BoltBack(true);
XZWA XYZ 2;
XZWB ABC 2;
XZWB D 3 A_DropCasing();
Goto ReadyLock;
Fire:
XZW2 A 1 A_MisterFire();
XZW3 EFGH 1;
XZW3 IJK 2;
Goto AutoCycle;
FireCluster:
XZW2 A 1;
XZW3 LMN 1;
XZW3 OPQRS 2;
Goto AutoCycle;
FireStream:
XZW2 A 1 A_MisterFireStream(1);
XZW3 T 1 A_MisterFireStream(2);
XZW3 U 1 A_MisterFireStream(3);
XZW3 VWXY 1;
XZW3 Z 2;
XZW4 ABC 2;
Goto AutoCycle;
FireOverpressure:
XZW2 A 2 A_MisterStartRail();
XZW4 DEF 5;
XZW4 F 1 A_MisterHoldRail();
Wait;
FireOverpressureRelease:
XZW4 F 1 A_MisterFireRail();
XZW4 GH 1;
XZW4 IJ 2;
XZW4 KLMN 3;
Goto AutoCycle;
FireOverpressureCancel:
XZW4 FED 3;
Goto Ready;
AltFire:
XZW2 A 1
{
if ( !invoker.gchambered || invoker.gfired )
return invoker.boltlock?ResolveState("AltReloadLock"):ResolveState("AltReload");
A_MisterFireGrenade();
return A_JumpIf(invoker.boltlock,"AltFireLock");
}
XZW4 OP 1;
XZW4 QR 2;
XZW4 ST 3;
XZW4 UVW 3;
Goto Ready;
AltFireLock:
XZWB E 1;
XZWC IJ 1;
XZWC KL 2;
XZWC MN 3;
XZWC OPQ 4;
Goto ReadyLock;
AltReload:
XZW2 A 3 A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 XYZ 3;
XZW5 A 3;
XZW5 B 2 A_GrenadeOpen();
XZW5 CD 2;
XZW5 EF 3;
XZW5 G 3 A_GrenadeDrop();
XZW5 H 3;
XZW5 I 3 A_GrenadeGrab();
XZW5 JKL 3;
XZW5 M 2 A_GrenadeIn();
XZW5 NOP 2;
XZW5 QRS 3;
XZW5 T 2 A_GrenadeClose();
XZW5 UVWXY 2;
XZW5 Z 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.15);
XZW6 ABCDEFG 2;
Goto Ready;
AltReloadLock:
XZWB E 3 A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
XZWC RSTU 3;
XZWC V 2 A_GrenadeOpen();
XZWC WX 2;
XZWC YZ 3;
XZWD A 3 A_GrenadeDrop();
XZWD B 3;
XZWD C 3 A_GrenadeGrab();
XZWD DEF 3;
XZWD G 2 A_GrenadeIn();
XZWD HIJ 2;
XZWD KLM 3;
XZWD N 2 A_GrenadeClose();
XZWD OPQRS 2;
XZWD T 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.15);
XZWD UVWXYZ 2;
XZWE A 2;
Goto ReadyLock;
Reload:
XZW2 A 2
{
if ( (invoker.clipcount >= invoker.default.clipcount) || ((invoker.Ammo1.Amount <= 0) && (CountInv("MisterRound") <= 0) && !sv_infiniteammo && !FindInventory("PowerInfiniteAmmo")) )
return invoker.boltlock?ResolveState("IdleLock"):ResolveState("Idle");
A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.boltlock,"ReloadLock");
}
XZW9 KLMNOPQRST 2;
XZW9 U 2 A_MagOut();
XZW9 VWXYZ 2;
XZWA ABC 2;
XZWA D 2 A_MagDrop();
XZWA E 2;
XZWA F 2 A_MagGrab();
XZWA GHI 2;
XZWA J 2 A_StartSound("mister/magin",CHAN_WEAPON,CHANF_OVERLAP);
XZWA K 2;
XZWA L 2 A_MagIn();
XZWA MN 2;
XZWA O 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.15);
XZWA PQRSTUV 2;
Goto Ready;
ReloadLock:
XZWB E 2;
XZWG WXYZ 2;
XZWH ABCDEF 2;
XZWH G 2 A_MagOut();
XZWH HIJKLMNO 2;
XZWH P 2 A_MagDrop();
XZWH Q 2;
XZWH R 2 A_MagGrab();
XZWH STU 2;
XZWH V 2 A_StartSound("mister/magin",CHAN_WEAPON,CHANF_OVERLAP);
XZWH W 2;
XZWH X 2 A_MagIn();
XZWH YZ 2;
XZWI ABCDEFG 2;
XZWI H 2 A_BoltForward(true);
XZWI I 2;
XZWI J 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.15);
XZWI KLMN 2;
XZWI OPQ 3;
Goto Ready;
Idle:
XZW2 A 2
{
A_StartSound("mister/deselect",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerCheckGun();
}
XZW7 JKLMNO 2;
XZW7 PQR 3;
XZW7 STU 4;
XZW7 V 2 A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
XZW7 WXY 2;
XZW7 Z 3;
XZW8 AB 3;
XZW8 CDEF 4;
XZW8 G 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.2);
XZW8 HIJK 2;
XZW8 L 3;
Goto Ready;
IdleLock:
XZWB E 2
{
A_StartSound("mister/deselect",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerCheckGun();
}
XZWE VWXYZ 2;
XZWF A 2;
XZWF BCD 3;
XZWF EFG 4;
XZWF H 2 A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
XZWF IJK 2;
XZWF LMN 3;
XZWF OPQR 4;
XZWF S 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.2);
XZWF TUVW 2;
XZWF X 3;
Goto ReadyLock;
Zoom:
XZW2 A 2
{
A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.boltlock,"ZoomLock");
}
XZW6 PQRSTUVW 2;
XZW6 X 2 A_FireSelect();
XZW6 YZ 2;
XZW7 A 2;
XZW7 B 2 A_JumpIf(player.cmd.buttons&BT_ZOOM,"ReZoom");
XZW7 CD 2;
XZW7 E 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.35);
XZW7 FGHI 2;
Goto Ready;
ReZoom:
XZW7 B 4; // smoother tween
Goto Zoom+6;
ZoomLock:
XZWB E 2;
XZWE BCDEFGHI 2;
XZWE J 2 A_FireSelect();
XZWE KLM 2;
XZWE N 2 A_JumpIf(player.cmd.buttons&BT_ZOOM,"ReZoomLock");
XZWE OP 2;
XZWE Q 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP,starttime:.35);
XZWE RSTU 2;
Goto ReadyLock;
ReZoomLock:
XZWE N 4; // smoother tween
Goto ZoomLock+6;
User1:
XZW2 A 2
{
A_StartSound("mister/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.boltlock,"User1Lock");
}
XZW8 MN 2;
XZW8 O 2
{
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW8 PQ 2;
XZW8 R 1;
XZW8 S 1 A_Parry(8);
XZW8 T 1;
XZW8 U 1 A_Melee(50,"demolitionist/whitm",1.25,1.2,1.2);
XZW8 VWXYZ 2;
XZW9 A 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW9 BCDEFGHIJ 2;
Goto Ready;
User1Lock:
XZWB E 2;
XZWF YZ 2;
XZWG A 2
{
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZWG BC 2;
XZWG D 1;
XZWG E 1 A_Parry(8);
XZWG F 1;
XZWG G 1 A_Melee(50,"demolitionist/whitm",1.25,1.2,1.2);
XZWG HIJKL 2;
XZWG M 2 A_StartSound("mister/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZWG NOPQRSTUV 2;
Goto ReadyLock;
Deselect:
XZW2 A 2
{
A_StartSound("mister/deselect",CHAN_WEAPON,CHANF_OVERLAP);
return A_JumpIf(invoker.boltlock,"DeselectLock");
}
XZW2 BCDE 2;
XZW2 FGHIJKLMNOP 1;
XZW2 P -1 A_FullLower();
Stop;
DeselectLock:
XZWB EFGHI 2;
XZWB JKLMNOPQRST 1;
XZWB T -1 A_FullLower();
Stop;
Flash:
XZW0 A 2 Bright
{
let psp = player.FindPSprite(PSP_FLASH);
psp.frame = Random[GunFlash](0,3);
let l = Spawn("MisterWeaponLight",pos);
l.target = self;
}
Stop;
FastFlash:
XZW0 A 2 Bright
{
let psp = player.FindPSprite(PSP_FLASH);
psp.frame = Random[GunFlash](0,3);
}
Stop;
AltFlash:
XZW0 E 2 Bright
{
let psp = player.FindPSprite(PSP_FLASH);
psp.frame = Random[GunFlash](4,7);
let l = Spawn("MisterWeaponLight",pos);
l.target = self;
}
Stop;
Spawn:
XZW1 A -1;
Stop;
}
}