Omnisight now handles 3d floors and midtextures, and exit line merging works properly. Reduced Pusher primary fire damage slightly. Cleared improper usages of GetAge().
1292 lines
37 KiB
Text
1292 lines
37 KiB
Text
// Inventory stuff
|
|
Mixin Class SWWMAutoUseFix
|
|
{
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
if ( GetClass() == item.GetClass() )
|
|
{
|
|
if ( Use(true) ) Amount--;
|
|
// sell excess if there's a price
|
|
if ( bALWAYSPICKUP && (Amount+item.Amount > MaxAmount) && (Stamina > 0) )
|
|
{
|
|
int sellprice = int(Stamina*.5);
|
|
SWWMScoreObj.Spawn(sellprice,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GOLD);
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player )
|
|
Console.Printf(StringTable.Localize("$SWWM_SELLEXTRA"),Owner.player.GetUserName(),GetTag(),sellprice);
|
|
}
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
}
|
|
|
|
Mixin Class SWWMOverlapPickupSound
|
|
{
|
|
// overlap sounds
|
|
override void PlayPickupSound( Actor toucher )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
if ( hnd.lastpickuptic[toucher.PlayerNumber()] == gametic )
|
|
return; // don't play if picked up on the same exact tic (overlapping items)
|
|
hnd.lastpickuptic[toucher.PlayerNumber()] = gametic;
|
|
}
|
|
double atten;
|
|
int flags = CHANF_OVERLAP|CHANF_MAYBE_LOCAL;
|
|
if ( bNoAttenPickupSound ) atten = ATTN_NONE;
|
|
else atten = ATTN_NORM;
|
|
if ( toucher && toucher.CheckLocalView() )
|
|
flags |= CHANF_NOPAUSE;
|
|
toucher.A_StartSound(PickupSound,CHAN_ITEM,flags,1.,atten);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
bool foundarmor = false;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( (i is 'SWWMArmor') && (i != self) ) foundarmor = true;
|
|
if ( !(i is 'SWWMArmor') || (i == self) || (SWWMArmor(i).priority < priority) ) continue;
|
|
found = i;
|
|
}
|
|
if ( !found && !foundarmor )
|
|
{
|
|
// check if first item in inventory is health or a sandwich
|
|
if ( (other.Inv is 'SWWMHealth') || (other.Inv is 'GrilledCheeseSandwich') )
|
|
{
|
|
// place ourselves before it
|
|
Inv = other.Inv;
|
|
other.Inv = self;
|
|
return;
|
|
}
|
|
// find first item with health or sandwich after it
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( (i == self) || (!(i.Inv is 'SWWMHealth' ) && !(i.Inv is 'GrilledCheeseSandwich')) ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
if ( !found && !foundarmor )
|
|
{
|
|
// find last of either invinciball or ragekit power
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'InvinciballPower') && !(i is 'RagekitPower') ) 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;
|
|
}
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
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);
|
|
int tgive = 0;
|
|
bool acc = CVar.GetCVar('swwm_accdamage',players[consoleplayer]).GetBool();
|
|
while ( (amount <= (MaxAmount-default.Amount)) && (Owner.CountInv(parent) > 0) )
|
|
{
|
|
if ( acc ) tgive += default.Amount;
|
|
else SWWMScoreObj.Spawn(default.Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
|
|
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;
|
|
}
|
|
if ( acc ) SWWMScoreObj.Spawn(tgive,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
|
|
}
|
|
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;
|
|
Mixin SWWMOverlapPickupSound;
|
|
|
|
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();
|
|
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());
|
|
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;
|
|
}
|
|
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;
|
|
Mixin SWWMOverlapPickupSound;
|
|
|
|
// can't use the Health class for whatever reason
|
|
// nice parser you got there I guess?
|
|
Class<Inventory> giveme;
|
|
|
|
Property GiveHealth : giveme;
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find last health item that's better than us
|
|
Inventory found = null;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'SWWMHealth') || (i == self) || (GetDefaultByType(SWWMHealth(i).giveme).Amount < GetDefaultByType(giveme).Amount) ) continue;
|
|
found = i;
|
|
}
|
|
if ( !found )
|
|
{
|
|
// find last armor item
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'SWWMArmor') ) continue;
|
|
found = i;
|
|
}
|
|
}
|
|
if ( !found )
|
|
{
|
|
// check if the first item in inventory is a sandwich
|
|
if ( other.Inv is 'GrilledCheeseSandwich' )
|
|
{
|
|
// place ourselves before it
|
|
Inv = other.Inv;
|
|
other.Inv = self;
|
|
return;
|
|
}
|
|
// find first item next to a sandwich
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( (i == self) || !(i.Inv is 'GrilledCheeseSandwich') ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
if ( !found )
|
|
{
|
|
// find last of either invinciball or ragekit power
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'InvinciballPower') && !(i is 'RagekitPower') ) continue;
|
|
found = i;
|
|
}
|
|
}
|
|
if ( !found ) return;
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
|
|
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 ( 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_BLUE);
|
|
return true;
|
|
}
|
|
|
|
virtual void AutoUseExtra()
|
|
{
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
}
|
|
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( Owner.ApplyDamageFactor(damageType,damage) <= 0 )
|
|
return; // this damage type is ignored by the player, so it does not affect us
|
|
if ( damageType == 'EndLevel' )
|
|
return; // don't trigger on endlevel damage
|
|
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 ( (Owner.Health-damage <= (GetDefaultByType(giveme).MaxAmount-GetDefaultByType(giveme).Amount)) )
|
|
{
|
|
newdamage = damage;
|
|
if ( ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
|
|
int tgive = 0;
|
|
bool acc = CVar.GetCVar('swwm_accdamage',players[consoleplayer]).GetBool();
|
|
while ( (Amount > 0) && (newdamage > 0) )
|
|
{
|
|
if ( acc ) tgive += GetDefaultByType(giveme).Amount;
|
|
else SWWMScoreObj.Spawn(GetDefaultByType(giveme).Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_BLUE);
|
|
newdamage = newdamage-GetDefaultByType(giveme).Amount;
|
|
if ( newdamage < 0 ) Owner.GiveBody(-newdamage,GetDefaultByType(giveme).MaxAmount);
|
|
newdamage = max(0,newdamage);
|
|
AutoUseExtra();
|
|
Amount--;
|
|
}
|
|
if ( acc ) SWWMScoreObj.Spawn(tgive,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_BLUE);
|
|
}
|
|
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;
|
|
+DROPOFF;
|
|
+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 = Random[Junk](-30,30);
|
|
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;
|
|
+NOINTERACTION;
|
|
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;
|
|
+NOINTERACTION;
|
|
}
|
|
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 BigPunchSplash : Actor
|
|
{
|
|
Default
|
|
{
|
|
DamageType 'Melee';
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SWWMHandler.DoBlast(self,120,40000,target);
|
|
A_Explode(special1,120,0);
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class BigPunchImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
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;
|
|
+NOINTERACTION;
|
|
}
|
|
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;
|
|
+NOINTERACTION;
|
|
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()
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
Class UseList
|
|
{
|
|
Line hitline;
|
|
int hitside;
|
|
Actor hitactor;
|
|
Vector3 pos;
|
|
}
|
|
|
|
Class UseLineTracer : LineTracer
|
|
{
|
|
Array<UseList> uses;
|
|
Array<Line> glass;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
let u = new("UseList");
|
|
u.hitline = null;
|
|
u.hitactor = Results.HitActor;
|
|
u.pos = Results.HitPos;
|
|
uses.Push(u);
|
|
return TRACE_Skip;
|
|
}
|
|
if ( Results.HitType == TRACE_HitWall )
|
|
{
|
|
if ( Results.HitLine.Activation&(SPAC_Use|SPAC_UseThrough) )
|
|
{
|
|
let u = new("UseList");
|
|
u.hitline = Results.HitLine;
|
|
u.hitside = Results.Side;
|
|
u.hitactor = null;
|
|
u.pos = Results.HitPos;
|
|
uses.Push(u);
|
|
}
|
|
if ( Results.Tier == TIER_Middle )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything|Line.ML_BlockUse)) )
|
|
return TRACE_Stop;
|
|
if ( Results.HitLine.special == GlassBreak ) // fuck glass
|
|
glass.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
// Base class for all SWWM Weapons
|
|
Class SWWMWeapon : Weapon abstract
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
|
|
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 bool HandlePickup( Inventory item )
|
|
{
|
|
if ( (GetClass() == item.GetClass()) )
|
|
{
|
|
int oldammo1 = Ammo1?Ammo1.Amount:0;
|
|
int oldammo2 = Ammo2?Ammo2.Amount:0;
|
|
if ( Weapon(item).PickupForAmmo(self) )
|
|
item.bPickupGood = true;
|
|
if ( (Amount+item.Amount > MaxAmount) && (Stamina > 0) )
|
|
{
|
|
// sell excess
|
|
int sellprice = Stamina;
|
|
// subtract prices of ammo given
|
|
if ( Ammo1 )
|
|
{
|
|
int ammogiven1 = Ammo1.Amount-oldammo1;
|
|
if ( ammogiven1 > 0 )
|
|
sellprice -= int(Ammo1.Stamina*(1.+.75*(ammogiven1-1)));
|
|
}
|
|
if ( Ammo2 )
|
|
{
|
|
int ammogiven2 = Ammo2.Amount-oldammo2;
|
|
if ( ammogiven2 > 0 )
|
|
sellprice -= int(Ammo2.Stamina*(1.+.75*(ammogiven2-1)));
|
|
}
|
|
sellprice = int(sellprice*.5);
|
|
SWWMScoreObj.Spawn(sellprice,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GOLD);
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player )
|
|
Console.Printf(StringTable.Localize("$SWWM_SELLEXTRA"),Owner.player.GetUserName(),GetTag(),sellprice);
|
|
item.bPickupGood = true;
|
|
}
|
|
return true;
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
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);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA2);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA3);
|
|
Super.DetachFromOwner();
|
|
}
|
|
override void OwnerDied()
|
|
{
|
|
if ( Owner.player && (Owner.player.ReadyWeapon == self) )
|
|
{
|
|
Owner.A_StopSound(CHAN_WEAPON);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA2);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA3);
|
|
}
|
|
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()
|
|
{
|
|
}
|
|
// extra drawing, usually scopes
|
|
virtual ui void RenderUnderlay( RenderEvent e )
|
|
{
|
|
}
|
|
// animations
|
|
action void A_PlayerFire()
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) ) demo.PlayFire();
|
|
}
|
|
action void A_PlayerMelee( bool bFast = false )
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) )
|
|
{
|
|
if ( bFast ) demo.PlayFastMelee();
|
|
else demo.PlayMelee();
|
|
}
|
|
}
|
|
action void A_PlayerReload()
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) ) demo.PlayReload();
|
|
}
|
|
action void A_PlayerCheckGun()
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) ) demo.PlayCheckGun();
|
|
}
|
|
// 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, String hitsound = "", double rangemul = 1. )
|
|
{
|
|
FTranslatedLineTarget t;
|
|
double slope = AimLineAttack(angle,1.5*DEFMELEERANGE*rangemul,t,0.,ALF_CHECK3D);
|
|
FLineTraceData d;
|
|
LineTrace(angle,1.5*DEFMELEERANGE*rangemul,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;
|
|
}
|
|
if ( !d.HitActor.bDORMANT ) // lol oops
|
|
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);
|
|
}
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",d.HitLocation);
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
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((hitsound!="")?hitsound:bloodless?"demolitionist/punch":"demolitionist/punchf",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_AlertMonsters(300);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
action void A_Melee( int dmg = 40, String hitsound = "", double rangemul = 1. )
|
|
{
|
|
// temporarily disable parry field so we can trace through
|
|
if ( invoker.pfield ) invoker.pfield.bSHOOTABLE = false;
|
|
// check for usables
|
|
let ut = new("UseLineTracer");
|
|
ut.uses.Clear();
|
|
ut.glass.Clear();
|
|
ut.Trace(Vec3Offset(0,0,player.viewheight),CurSector,(cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch)),DEFMELEERANGE*rangemul,0);
|
|
for ( int i=0; i<ut.uses.Size(); i++ )
|
|
{
|
|
if ( ut.uses[i].hitactor )
|
|
{
|
|
if ( (ut.uses[i].hitactor == self) || (ut.uses[i].hitactor is 'Demolitionist') ) continue;
|
|
if ( ut.uses[i].hitactor.Used(self) ) break;
|
|
}
|
|
else if ( ut.uses[i].hitline )
|
|
{
|
|
int locknum = SWWMUtility.GetLineLock(ut.uses[i].hitline);
|
|
if ( !locknum || CheckKeys(locknum,false,true) )
|
|
ut.uses[i].hitline.RemoteActivate(self,ut.uses[i].hitside,SPAC_Use,ut.uses[i].pos);
|
|
if ( !(ut.uses[i].hitline.activation&SPAC_UseThrough) ) break;
|
|
}
|
|
}
|
|
// fuck glass
|
|
for ( int i=0; i<ut.glass.Size(); i++ )
|
|
ut.glass[i].Activate(self,0,SPAC_Impact);
|
|
let raging = RagekitPower(FindInventory("RagekitPower"));
|
|
int maxang = raging?18:12;
|
|
for ( int i=0; i<maxang; i++ )
|
|
{
|
|
if ( TryMelee(angle+i*(45./16),dmg,hitsound,rangemul) || TryMelee(angle-i*(45./16),dmg,hitsound,rangemul) )
|
|
{
|
|
if ( invoker.pfield ) invoker.pfield.bSHOOTABLE = true;
|
|
return;
|
|
}
|
|
}
|
|
if ( invoker.pfield ) invoker.pfield.bSHOOTABLE = true;
|
|
// check for walls instead
|
|
FTranslatedLineTarget t;
|
|
double slope = AimLineAttack(angle,DEFMELEERANGE*rangemul,t,0.,ALF_CHECK3D);
|
|
FLineTraceData d;
|
|
LineTrace(angle,DEFMELEERANGE*rangemul,slope,TRF_THRUACTORS,player.viewheight,data:d);
|
|
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);
|
|
}
|
|
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",d.HitLocation+HitNormal*4);
|
|
p.angle = atan2(HitNormal.y,HitNormal.x);
|
|
p.pitch = asin(-HitNormal.z);
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",d.HitLocation+HitNormal*4);
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
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":(hitsound!="")?hitsound:"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;
|
|
}
|
|
override Inventory CreateTossable( int amt )
|
|
{
|
|
// disallow dropping if weapon isn't ready for switching
|
|
if ( (Owner.player.ReadyWeapon == self) && (!(Owner.player.WeaponState&WF_WEAPONSWITCHOK) || (Owner.player.WeaponState&WF_DISABLESWITCH)) )
|
|
return null;
|
|
return Super.CreateTossable(amt);
|
|
}
|
|
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;
|
|
}
|
|
}
|