swwmgz_m/zscript/swwm_inventory.zsc
Marisa Kirisame faddb088c1 Hellblazer fully implemented.
Rebalanced Candygun.
Added vanilla boss healthbars + vanilla boss boosting (both optional).
Adjustments so the E1M8 exit and similar work fine with this mod.
Various language changes (mostly vanilla monster tags).
Doubled damage of Pusher altfire, so it matches the projectile's.
Adjusted minimum fall speed for stomping.
Other small fixed and adjustments.
2020-04-20 21:42:43 +02:00

988 lines
28 KiB
Text

// Inventory stuff
Mixin Class SWWMAutoUseFix
{
override bool HandlePickup( Inventory item )
{
if ( GetClass() == item.GetClass() )
{
if ( Use(true) ) Amount--;
}
return Super.HandlePickup(item);
}
}
// Base class for all SWWM Armors
Class SWWMArmor : Armor abstract
{
int priority;
String drainmsg;
Class<SWWMSpareArmor> parent;
Property ArmorPriority : priority;
Property DrainMessage : drainmsg;
Property GiverArmor : parent;
Default
{
+INVENTORY.AUTOACTIVATE;
+INVENTORY.UNTOSSABLE;
+INVENTORY.UNDROPPABLE;
+INVENTORY.KEEPDEPLETED;
+INVENTORY.ALWAYSPICKUP;
}
override void AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
// find last armor that's better than us
Inventory found = null;
for ( Inventory i=other.Inv; i; i=i.Inv )
{
if ( !(i is 'SWWMArmor') || (i == self) || (SWWMArmor(i).priority < priority) ) continue;
found = i;
}
if ( !found ) return;
// place ourselves right after it
Inventory saved = found.Inv;
found.Inv = self;
other.Inv = Inv;
Inv = saved;
}
// for subclasses
virtual int HandleDamage( int damage, Name damageType, int flags )
{
return damage;
}
// sadly we can'y use the extra AbsorbDamage arguments until GZDoom gets updated
//override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( !passive ) return;
if ( (damagetype == 'Drowning') || (damagetype == 'Falling') || (damagetype == 'Poison') || (damagetype == 'PoisonCloud') )
return; // these go through armor and get ignored by the player
int saved;
if ( (amount > 0) && !DamageTypeDefinition.IgnoreArmor(damageType) && (damage > 0) )
{
SWWMHandler.DoFlash(Owner,Color(int(clamp(damage*.15,1,16)),255,224,192),3);
Owner.A_StartSound("armor/hit",CHAN_BODY,CHANF_DEFAULT,clamp(damage*.03,0.,1.),2.5);
saved = HandleDamage(damage,damageType,flags);
int healed = max(0,saved-damage);
saved = min(saved,damage);
if ( amount <= saved ) saved = amount;
newdamage -= saved;
if ( healed > 0 ) Owner.GiveBody(healed);
amount -= saved;
damage = newdamage;
bool shouldautouse = false;
if ( swwm_enforceautousearmor == 1 ) shouldautouse = true;
else if ( swwm_enforceautousearmor == -1 ) shouldautouse = false;
else shouldautouse = CVar.GetCVar('swwm_autousearmor',Owner.player).GetBool();
if ( (amount <= (MaxAmount-default.Amount)) && (Owner.CountInv(parent) > 0) && shouldautouse )
{
if ( GetDefaultByType(parent).UseSound ) Owner.A_StartSound(GetDefaultByType(parent).UseSound,CHAN_ITEMEXTRA,CHANF_DEFAULT,.6);
while ( (amount <= (MaxAmount-default.Amount)) && (Owner.CountInv(parent) > 0) )
{
Amount += default.Amount;
Owner.TakeInventory(parent,1);
// absorb the extra damage too
saved = HandleDamage(damage,damageType,flags);
healed = max(0,saved-damage);
saved = min(saved,damage);
if ( amount <= saved ) saved = amount;
newdamage -= saved;
if ( healed > 0 ) Owner.GiveBody(healed);
amount -= saved;
damage = newdamage;
}
}
else if ( amount <= 0 )
{
if ( damage > 0 ) newdamage = ApplyDamageFactors(GetClass(),damageType,damage,damage);
if ( Owner.CheckLocalView() && (drainmsg != "") ) Console.Printf(StringTable.Localize(drainmsg));
DepleteOrDestroy();
return;
}
}
if ( damage > 0 ) newdamage = ApplyDamageFactors(GetClass(),damageType,damage,damage);
}
}
// gives armor when used
Class SWWMSpareArmor : Inventory abstract
{
Mixin SWWMAutoUseFix;
Class<SWWMArmor> giveme;
Property GiveArmor : giveme;
override bool Use( bool pickup )
{
bool shouldautouse = false;
if ( swwm_enforceautousearmor == 1 ) shouldautouse = true;
else if ( swwm_enforceautousearmor == -1 ) shouldautouse = false;
else shouldautouse = CVar.GetCVar('swwm_autousearmor',Owner.player).GetBool();
if ( pickup && !shouldautouse ) return false;
let cur = Owner.FindInventory(giveme);
if ( !cur || (!pickup && (cur.Amount < cur.MaxAmount)) || (GetDefaultByType(giveme).Amount+cur.Amount <= cur.MaxAmount) )
{
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
Owner.GiveInventory(giveme,GetDefaultByType(giveme).Amount);
SWWMHandler.ArmorFlash(Owner.PlayerNumber());
return true;
}
return false;
}
Default
{
+INVENTORY.INVBAR;
+INVENTORY.ISARMOR;
+INVENTORY.AUTOACTIVATE;
Inventory.MaxAmount 5;
Inventory.InterHubAmount 5;
+FLOATBOB;
FloatBobStrength 0.25;
}
}
Class SWWMHealth : Inventory abstract
{
Mixin SWWMAutoUseFix;
// can't use the Health class for whatever reason
// nice parser you got there I guess?
Class<Inventory> giveme;
Property GiveHealth : giveme;
override bool Use( bool pickup )
{
bool shouldautouse = false;
if ( swwm_enforceautousehealth == 1 ) shouldautouse = true;
else if ( swwm_enforceautousehealth == -1 ) shouldautouse = false;
else shouldautouse = CVar.GetCVar('swwm_autousehealth',Owner.player).GetBool();
if ( pickup && !shouldautouse ) return false;
if ( Owner.Health >= GetDefaultByType(giveme).MaxAmount ) return false;
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
SWWMHandler.HealthFlash(Owner.PlayerNumber());
Owner.GiveInventory(giveme,GetDefaultByType(giveme).Amount);
SWWMScoreObj.Spawn(GetDefaultByType(giveme).Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
return true;
}
virtual void AutoUseExtra()
{
}
override void DoEffect()
{
Super.DoEffect();
if ( Amount <= 0 ) DepleteOrDestroy();
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( damageType == 'EndLevel' ) return;
bool shouldautouse = false;
if ( swwm_enforceautousehealth == 1 ) shouldautouse = true;
else if ( swwm_enforceautousehealth == -1 ) shouldautouse = false;
else shouldautouse = CVar.GetCVar('swwm_autousehealth',Owner.player).GetBool();
if ( !shouldautouse && !bBIGPOWERUP ) return;
if ( passive && (Owner.Health-damage <= (GetDefaultByType(giveme).MaxAmount-GetDefaultByType(giveme).Amount)) )
{
newdamage = damage;
if ( ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
while ( (Amount > 0) && (newdamage > 0) )
{
newdamage = newdamage-GetDefaultByType(giveme).Amount;
if ( newdamage < 0 ) Owner.GiveBody(-newdamage,GetDefaultByType(giveme).MaxAmount);
newdamage = max(0,newdamage);
AutoUseExtra();
Amount--;
}
}
else newdamage = damage;
}
Default
{
+INVENTORY.INVBAR;
+INVENTORY.ISHEALTH;
+INVENTORY.AUTOACTIVATE;
Inventory.MaxAmount 5;
Inventory.InterHubAmount 5;
Inventory.UseSound "misc/health_pkup";
+FLOATBOB;
FloatBobStrength 0.25;
}
}
// Base casing classes
Class SWWMCasing : Actor abstract
{
int deadtimer, numbounces;
double pitchvel, anglevel;
double heat;
Default
{
Radius 2;
Height 2;
+NOBLOCKMAP;
+MISSILE;
+MOVEWITHSECTOR;
+THRUACTORS;
+USEBOUNCESTATE;
+INTERPOLATEANGLES;
+NOTELEPORT;
+ROLLSPRITE;
+ROLLCENTER;
Mass 1;
Gravity 0.35;
BounceType "Hexen";
WallBounceFactor 0.65;
BounceFactor 0.65;
BounceSound "explodium/casing";
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
deadtimer = 0;
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
heat = 1.0;
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
if ( InStateSequence(CurState,ResolveState("Death")) )
{
deadtimer++;
if ( deadtimer > 300 ) A_FadeOut(0.05);
return;
}
heat -= 0.05;
if ( heat <= 0 ) return;
let s = Spawn("SWWMSmallSmoke",pos);
s.alpha *= heat;
}
States
{
Spawn:
XZW1 A 1
{
angle += anglevel;
pitch += pitchvel;
}
Loop;
Bounce:
#### # 0
{
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
vel = (vel.unit()+(FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2))).unit()*vel.length();
if ( numbounces && ((numbounces > 3) || (Random[Junk](1,20) < 17) || (vel.z > -1.4)) )
{
ClearBounce();
ExplodeMissile();
}
numbounces++;
}
Goto Spawn;
Death:
#### # -1
{
pitch = roll = 0;
angle = FRandom[Junk](0,360);
}
Stop;
}
}
Class SWWMBulletImpact : Actor
{
Default
{
RenderStyle "Add";
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOTELEPORT;
Scale 0.25;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_SprayDecal("Pock",-20);
int numpt = int(Random[Junk](5,10)*scale.x*4);
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Junk](-.8,.8),FRandom[Junk](-.8,.8),FRandom[Junk](-.8,.8))).unit()*FRandom[Junk](0.1,1.2);
let s = Spawn("SWWMSmoke",pos+x*2);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Junk](128,192));
}
numpt = int(Random[Junk](3,8)*scale.x*4);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Junk](-1,1),FRandom[Junk](-1,1),FRandom[Junk](-1,1)).unit()*FRandom[Junk](2,8);
let s = Spawn("SWWMSpark",pos+x*2);
s.vel = pvel;
}
numpt = int(Random[Junk](2,5)*scale.x*4);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Junk](-1,1),FRandom[Junk](-1,1),FRandom[Junk](-1,1)).unit()*FRandom[Junk](2,8);
let s = Spawn("SWWMChip",pos+x*2);
s.vel = pvel;
}
if ( !Random[Junk](0,3) ) A_StartSound("bullet/ricochet",CHAN_VOICE,attenuation:2.5);
else A_StartSound("bullet/hit",CHAN_VOICE,attenuation:3.0);
Spawn("InvisibleSplasher",pos);
}
States
{
Spawn:
TNT1 A 1;
Stop;
}
}
Class SWWMWeaponLight : DynamicLight
{
int cnt;
Default
{
DynamicLight.Type "Point";
args 255,224,64,150;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
if ( target.player )
{
Vector3 x, y, z, origin;
[x, y, z] = swwm_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*12);
SetOrigin(origin,true);
}
else SetOrigin(target.pos,true);
if ( cnt++ > 2 ) Destroy();
}
}
Class PunchImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(2,2,2,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.3);
A_StartSound("demolitionist/punch",CHAN_VOICE,CHANF_DEFAULT,bAMBUSH?.6:1.);
A_SprayDecal("WallCrack",-20);
int numpt = Random[Ponch](5,10);
if ( bAMBUSH ) numpt /= 3;
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8))).unit()*FRandom[Ponch](.1,1.2);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Ponch](128,192));
}
numpt = Random[Ponch](4,12);
if ( bAMBUSH ) numpt /= 3;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Ponch](4,8);
if ( bAMBUSH ) numpt /= 3;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class BigPunchImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(8,8,8,18,0,600,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:.9);
A_StartSound("pusher/althit",CHAN_VOICE,CHANF_DEFAULT,bAMBUSH?.6:1.);
A_SprayDecal("BigWallCrack",-20);
int numpt = Random[Ponch](9,16);
if ( bAMBUSH ) numpt /= 3;
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8))).unit()*FRandom[Ponch](.1,1.2);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Ponch](128,192));
}
numpt = Random[Ponch](9,15);
if ( bAMBUSH ) numpt /= 3;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Ponch](9,16);
if ( bAMBUSH ) numpt /= 3;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class ReflectedBullet : SWWMCasing {}
Class ParriedBuff : Inventory
{
Default
{
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
Inventory.Amount 1;
Inventory.MaxAmount 1;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner.bMISSILE && !Owner.bSKULLFLY )
{
// lost soul is no longer attacking
Destroy();
return;
}
if ( special1 <= 0 ) return;
// smoke trail
Actor s;
if ( special1&1 )
{
s = Spawn("SWWMSmoke",Owner.pos);
s.vel = Owner.vel*.3+(FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](.1,.6);
s.scale *= 1.2;
s.alpha *= .3;
}
if ( special1 > 1 )
{
s = Spawn("SWWMSmoke",Owner.pos);
s.vel = Owner.vel*.3+(FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](.1,1.2);
s.scale *= 2.;
s.A_SetRenderStyle(s.alpha,STYLE_AddShaded);
s.SetShade(Color(4,2,1)*Random[Ponch](32,63));
}
}
}
// amplifies damage of parried projectiles
Class ParryDamageChecker : Inventory
{
Default
{
+Inventory.UNDROPPABLE;
+Inventory.UNTOSSABLE;
+Inventory.UNCLEARABLE;
Inventory.Amount 1;
Inventory.MaxAmount 1;
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
Inventory buff;
if ( inflictor && (buff=inflictor.FindInventory("ParriedBuff")) )
{
double mult;
if ( buff.special1 <= 1 ) mult = 1.5;
else if ( buff.special1 >= 2 ) mult = 8.;
if ( buff.special1&1 ) mult *= 2.;
newdamage = int(damage*mult);
}
}
}
Class ParryRing : Actor
{
Default
{
RenderStyle "Add";
Scale .1;
Alpha .3;
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+NOTELEPORT;
}
States
{
Spawn:
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 1 A_SetScale(scale.x*(1+specialf1));
Stop;
}
}
Class ParryField : Actor
{
int lasteggtic;
Default
{
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
+SHOOTABLE;
+NONSHOOTABLE;
+NOBLOOD;
+DONTTHRUST;
Health int.max;
Mass int.max;
Radius 20;
Height 40;
}
static bool ValidMissile( Actor a )
{
// fuuuuuuuuuuuuck why can't you just subclass them all from one base class, also wtf hexen, what's with all these things having +MISSILE
if ( (a is 'Rock1') || (a is 'Rock2') || (a is 'Rock3') || (a is 'Dirt1') || (a is 'Dirt2') || (a is 'Dirt3') || (a is 'Dirt4') || (a is 'Dirt5') || (a is 'Dirt6') || (a is 'GlassShard') || (a is 'PotteryBit') || (a is 'CorpseBloodDrip') || (a is 'Leaf1') || (a is 'HWaterDrip') || (a is 'IceGuyWisp1') || (a is 'WraithFX3') || (a is 'WraithFX4') || (a is 'Bat') || (a is 'SorcBall') )
return false;
return true;
}
override void Tick()
{
Super.Tick();
if ( !master )
{
Destroy();
return;
}
Vector3 x, y, z, origin;
[x, y, z] = swwm_CoordUtil.GetAxes(master.pitch,master.angle,master.roll);
origin = level.Vec3Offset(master.Vec2OffsetZ(0,0,master.player.viewz),x*20-(0,0,20));
SetOrigin(origin,true);
// check for projectiles to deflect
let ti = ThinkerIterator.Create("Actor");
Actor a;
while ( a = Actor(ti.Next()) )
{
if ( !((a.bMISSILE && (a.target != master) && ValidMissile(a)) || a.bSKULLFLY) || a.bTHRUACTORS || (level.Vec3Diff(a.pos,Vec3Offset(0,0,20)).length() > 80) ) continue;
Vector3 vdir = a.vel;
Vector3 dir = level.Vec3Diff(master.Vec2OffsetZ(0,0,pos.z),a.pos).unit();
if ( dir dot vdir > 0 ) continue; // already moving away
if ( a.bMISSILE )
{
// deflect directly to target
if ( !Random[Parry](0,2) && a.target )
dir = level.Vec3Diff(a.pos,a.target.Vec3Offset(0,0,a.target.height/2)).unit();
// push away
if ( a.bSEEKERMISSILE ) a.tracer = a.target;
a.target = master;
}
a.GiveInventory("ParriedBuff",1);
let buff = a.FindInventory("ParriedBuff");
double mvel = a.vel.length();
double nspeed = min(100,mvel*FRandom[Parry](1.2,1.4)+20);
a.angle = atan2(dir.y,dir.x);
a.pitch = asin(-dir.z);
let raging = RagekitPower(master.FindInventory("RagekitPower"));
if ( raging )
{
buff.special1 = 2;
nspeed = min(100,nspeed*2.);
raging.DoHitFX();
}
a.vel = dir*nspeed;
if ( a.bMISSILE ) a.speed = nspeed;
let i = Spawn(raging?"BigPunchImpact":"PunchImpact",a.pos);
i.target = master;
i.angle = atan2(dir.y,dir.x);
i.pitch = asin(-dir.z);
i.bAMBUSH = true;
A_QuakeEx(3,3,3,10,0,64,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.2);
A_StartSound("demolitionist/parry",CHAN_WEAPON);
if ( (level.maptime > lasteggtic) && !Random[Parry](0,4) )
{
for ( int i=1; i<6; i++ )
{
let r = Spawn("ParryRing",a.pos);
r.specialf1 = i*.04;
}
buff.special1++;
A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
lasteggtic = level.maptime+5;
}
}
if ( --special1 <= 0 ) Destroy();
}
override bool CanCollideWith( Actor other, bool passive )
{
return false;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
if ( (flags&DMG_INFLICTOR_IS_PUFF) && (source != master) )
{
Vector3 vdir = (cos(angle),sin(angle),0);
Vector3 vpos = inflictor.pos; // puff goes here
let raging = RagekitPower(master.FindInventory("RagekitPower"));
let i = Spawn(raging?"BigPunchImpact":"PunchImpact",vpos);
if ( raging ) raging.DoHitFX();
i.angle = angle+180;
i.bAMBUSH = true;
// deflect hitscan
A_StartSound("misc/ricochet",CHAN_VOICE,CHANF_OVERLAP,.7);
A_QuakeEx(3,3,3,10,0,64,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.2);
A_StartSound("demolitionist/parry",CHAN_WEAPON);
double mult = 1.5;
if ( raging ) mult = 8.;
if ( (level.maptime > lasteggtic) && !Random[Parry](0,4) )
{
for ( int i=1; i<6; i++ )
{
let r = Spawn("ParryRing",vpos);
r.specialf1 = i*.04;
}
mult *= 2.;
A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
lasteggtic = level.maptime+5;
}
// three options:
switch ( Random[Parry](0,7) )
{
case 0:
case 1:
case 2:
// 1. just block the bullet, making it bounce off
let b = Spawn("ReflectedBullet",vpos);
b.angle = angle;
b.target = master;
b.vel = (-vdir+(FRandom[Parry](-.4,.4),FRandom[Parry](-.4,.4),FRandom[Parry](-.4,.4))).unit()*FRandom[Parry](1,2)*mult+(0,0,4);
break;
case 3:
case 4:
case 5:
// 2. ricochet in random direction
Vector3 rdir = (-vdir+(FRandom[Parry](-.6,.6),FRandom[Parry](-.6,.6),FRandom[Parry](-.6,.6))).unit();
bSHOOTABLE = false;
master.LineAttack(atan2(rdir.y,rdir.x),8000,asin(-rdir.z),int(damage*mult),mod,inflictor.GetClass(),LAF_ABSPOSITION,null,vpos.z,vpos.x,vpos.y);
bSHOOTABLE = true;
break;
case 6:
case 7:
// 3. ricochet directly back to source
Vector3 tdir = source?(level.Vec3Diff(vpos,source.Vec3Offset(0,0,source.height/2))).unit():(-vdir);
bSHOOTABLE = false;
master.LineAttack(atan2(tdir.y,tdir.x),8000,asin(-tdir.z),int(damage*mult),mod,inflictor.GetClass(),LAF_ABSPOSITION,null,vpos.z,vpos.x,vpos.y);
bSHOOTABLE = true;
break;
}
}
return 0;
}
States
{
Spawn:
TNT1 A -1;
Stop;
}
}
// Base class for all SWWM Weapons
Class SWWMWeapon : Weapon abstract
{
private int SWeaponFlags;
Actor pfield; // instance of parry field for current melee attack
FlagDef NoFirstGive : SWeaponFlags, 0; // don't give ammo on first pickup (for weapons with a clip count)
override void AttachToOwner( Actor other )
{
Inventory.AttachToOwner(other);
Ammo1 = AddAmmo(Owner,AmmoType1,bNoFirstGive?0:AmmoGive1);
Ammo2 = AddAmmo(Owner,AmmoType2,bNoFirstGive?0:AmmoGive2);
SisterWeapon = AddWeapon(SisterWeaponType);
if ( Owner.player )
{
if ( !Owner.player.GetNeverSwitch() && !bNo_Auto_Switch )
Owner.player.PendingWeapon = self;
if ( Owner.player.mo == players[consoleplayer].camera )
StatusBar.ReceivedWeapon(self);
}
GivenAsMorphWeapon = false;
}
override void DetachFromOwner()
{
Owner.A_StopSound(CHAN_WEAPON);
Owner.A_StopSound(CHAN_WEAPONEXTRA);
Super.DetachFromOwner();
}
override void OwnerDied()
{
if ( Owner.player && (Owner.player.ReadyWeapon == self) )
{
Owner.A_StopSound(CHAN_WEAPON);
Owner.A_StopSound(CHAN_WEAPONEXTRA);
}
A_ClearRefire();
Super.OwnerDied();
}
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( mod == 'Melee' ) return StringTable.Localize("$O_MELEE");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
// draw ammo on hud above weapon box
virtual ui void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
{
}
// HUD-side ticking
virtual ui void HudTick()
{
}
// instant raise/lower
action void A_FullRaise()
{
if ( !player ) return;
if ( player.PendingWeapon != WP_NOCHANGE )
{
player.mo.DropWeapon();
return;
}
if ( !player.ReadyWeapon ) return;
let psp = player.GetPSprite(PSP_WEAPON);
psp.y = WEAPONTOP;
}
action void A_FullLower()
{
if ( !player ) return;
if ( !player.ReadyWeapon )
{
player.mo.BringUpWeapon();
return;
}
let psp = player.GetPSprite(PSP_WEAPON);
psp.y = WEAPONBOTTOM;
if ( player.playerstate == PST_DEAD )
{
// Player is dead, so don't bring up a pending weapon
// Player is dead, so keep the weapon off screen
player.SetPSprite(PSP_FLASH,null);
psp.SetState(player.ReadyWeapon.FindState('DeadLowered'));
return;
}
// [RH] Clear the flash state. Only needed for Strife.
player.SetPSprite(PSP_FLASH,null);
player.mo.BringUpWeapon();
}
action void A_Parry( int duration )
{
Vector3 x, y, z, origin;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),x*20-(0,0,20));
if ( invoker.pfield ) invoker.pfield.Destroy();
invoker.pfield = Spawn("ParryField",origin);
invoker.pfield.master = self;
invoker.pfield.special1 = duration;
if ( !FindInventory("ParryDamageChecker") )
GiveInventory("ParryDamageChecker",1); // need this so parried projectiles deal extra damage
}
private action bool TryMelee( double angle, int dmg )
{
FTranslatedLineTarget t;
double slope = AimLineAttack(angle,1.5*DEFMELEERANGE,t,0.,ALF_CHECK3D);
FLineTraceData d;
LineTrace(angle,1.5*DEFMELEERANGE,slope,0,player.viewheight,data:d);
bool raging = CountInv("RagekitPower");
if ( d.HitType == TRACE_HitActor )
{
bool bloodless = true;
double diff = deltaangle(self.angle,AngleTo(d.HitActor));
self.angle += clamp(diff,-5.,5.);
SWWMHandler.DoKnockback(d.HitActor,d.HitDir+(0,0,.2),dmg*2000);
if ( raging )
{
invoker.bEXTREMEDEATH = true;
invoker.bNOEXTREMEDEATH = false;
}
else
{
invoker.bEXTREMEDEATH = false;
invoker.bNOEXTREMEDEATH = true;
}
d.HitActor.DaggerAlert(self);
int flg = DMG_USEANGLE|DMG_THRUSTLESS;
if ( raging ) flg |= DMG_FOILINVUL;
dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Melee',flg,atan2(d.HitDir.y,d.HitDir.x));
invoker.bEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
invoker.bNOEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
int quakin = raging?8:2;
if ( d.HitActor.player ) d.HitActor.A_QuakeEx(quakin,quakin,quakin,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.125*quakin);
if ( !d.HitActor.bNOBLOOD && (raging || !d.HitActor.bINVULNERABLE) )
{
d.HitActor.TraceBleed(dmg,invoker);
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
bloodless = false;
}
else
{
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",d.HitLocation);
p.angle = atan2(-d.HitDir.y,-d.HitDir.x);
}
A_QuakeEx(quakin/2,quakin/2,quakin/2,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.06*quakin);
if ( raging ) A_StartSound(bloodless?"pusher/althit":"pusher/altmeat",CHAN_WEAPON,CHANF_OVERLAP);
else A_StartSound(bloodless?"demolitionist/punch":"demolitionist/punchf",CHAN_WEAPON,CHANF_OVERLAP);
A_AlertMonsters(300);
return true;
}
return false;
}
action void A_Melee( int dmg = 40 )
{
// temporarily disable parry field so we can trace through
if ( invoker.pfield ) invoker.pfield.bSHOOTABLE = false;
let raging = RagekitPower(FindInventory("RagekitPower"));
int maxang = raging?18:12;
for ( int i=0; i<maxang; i++ )
{
if ( TryMelee(angle+i*(45./16),dmg) || TryMelee(angle-i*(45./16),dmg) )
{
if ( invoker.pfield ) invoker.pfield.bSHOOTABLE = true;
return;
}
}
// check for walls instead
FTranslatedLineTarget t;
double slope = AimLineAttack(angle,DEFMELEERANGE,t,0.,ALF_CHECK3D);
FLineTraceData d;
LineTrace(angle,DEFMELEERANGE,slope,TRF_THRUACTORS,player.viewheight,data:d);
if ( invoker.pfield ) invoker.pfield.bSHOOTABLE = true;
if ( d.HitType == TRACE_HitNone ) return;
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;
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation+HitNormal*4);
int locknum = SWWMUtility.GetLineLock(d.HitLine);
if ( !locknum || CheckKeys(locknum,false,true) )
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Use,d.HitLocation+HitNormal*4);
}
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",d.HitLocation+HitNormal*4);
p.angle = atan2(HitNormal.y,HitNormal.x);
p.pitch = asin(-HitNormal.z);
int quakin = raging?4:1;
A_QuakeEx(quakin,quakin,quakin,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12*quakin);
A_StartSound(raging?"pusher/althit":"demolitionist/punch",CHAN_WEAPON,CHANF_OVERLAP);
A_AlertMonsters(100);
if ( raging ) raging.DoHitFX();
}
override void PlayUpSound( Actor origin )
{
if ( UpSound ) origin.A_StartSound(UpSound,CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_SWWMFlash( StateLabel flashlabel = null )
{
if ( !player || !player.ReadyWeapon )
return;
Weapon weap = player.ReadyWeapon;
State flashstate = null;
if ( !flashlabel )
{
if ( weap.bAltFire )
flashstate = weap.FindState('AltFlash');
if ( !flashstate )
flashstate = weap.FindState('Flash');
}
else flashstate = weap.FindState(flashlabel);
player.SetPSprite(PSP_FLASH,flashstate);
A_OverlayFlags(PSP_FLASH,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true);
A_OverlayRenderStyle(PSP_FLASH,STYLE_Add);
}
// tells the SWWM HUD that this weapon has ammo available
virtual clearscope bool ReportHUDAmmo()
{
return (!Ammo1||(Ammo1.Amount>0)||(Ammo2&&(Ammo2.Amount>0)));
}
// tells the Embiggener that this weapon uses the specified ammo type
// even if it is not its primary one
virtual clearscope bool UsesAmmo( Class<Ammo> kind )
{
return (AmmoType1&&(kind is AmmoType1))||(AmmoType2&&(kind is AmmoType2));
}
override void ModifyDropAmount( int dropamount )
{
Super.ModifyDropAmount(dropamount);
if ( (AmmoGive1 <= 0) && (default.AmmoGive1 > 0) )
AmmoGive1 = 1;
if ( (AmmoGive2 <= 0) && (default.AmmoGive2 > 0) )
AmmoGive2 = 1;
}
Default
{
Weapon.BobStyle "Alpha";
Weapon.BobSpeed 3.0;
Weapon.BobRangeX 0.5;
Weapon.BobRangeY 0.2;
Weapon.YAdjust 0;
+WEAPON.NOALERT;
+WEAPON.NODEATHINPUT;
+FLOATBOB;
FloatBobStrength 0.25;
}
}