1551 lines
46 KiB
Text
1551 lines
46 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);
|
|
}
|
|
}
|
|
|
|
Class CrossLineFinder : LineTracer
|
|
{
|
|
Array<Line> clines;
|
|
Array<int> csides;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( (Results.HitType == TRACE_HitWall) && (Results.HitLine.activation&SPAC_Cross) )
|
|
{
|
|
clines.Push(Results.HitLine);
|
|
csides.Push(Results.Side);
|
|
}
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
|
|
Mixin Class SWWMUseToPickup
|
|
{
|
|
// allow pickup by use
|
|
override bool Used( Actor user )
|
|
{
|
|
Vector3 itempos = Vec3Offset(0,0,Height/2),
|
|
userpos = user.Vec2OffsetZ(0,0,user.player.viewz);
|
|
// test vertical range
|
|
Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2));
|
|
double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2);
|
|
if ( abs(diff.z) > rang ) return false;
|
|
Touch(user);
|
|
// we got picked up
|
|
if ( bDestroyed || Owner || !bSPECIAL )
|
|
{
|
|
Vector3 tracedir = level.Vec3Diff(userpos,itempos);
|
|
double dist = tracedir.length();
|
|
tracedir /= dist;
|
|
let cf = new("CrossLineFinder");
|
|
cf.Trace(userpos,level.PointInSector(userpos.xy),tracedir,dist,0);
|
|
// trigger all player cross lines found between user and item
|
|
for ( int i=0; i<cf.clines.Size(); i++ )
|
|
cf.clines[i].Activate(user,cf.csides[i],SPAC_Cross);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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) )
|
|
return;
|
|
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);
|
|
if ( (swwm_strictuntouchable == 1) && (saved > 0) && Owner.player )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.tookdamage[Owner.PlayerNumber()] = true;
|
|
}
|
|
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 ( Owner.CheckLocalView() && (drainmsg != "") ) Console.Printf(StringTable.Localize(drainmsg));
|
|
DepleteOrDestroy();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// gives armor when used
|
|
Class SWWMSpareArmor : Inventory abstract
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMUseToPickup;
|
|
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;
|
|
Inventory.PickupFlash "SWWMGreenPickupFlash";
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|
|
|
|
Class SWWMHealth : Inventory abstract
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMUseToPickup;
|
|
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);
|
|
AutoUseExtra(false);
|
|
return true;
|
|
}
|
|
|
|
virtual void AutoUseExtra( bool recursive )
|
|
{
|
|
}
|
|
|
|
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;
|
|
// lesser healing items can't prevent lethal damage
|
|
// bigger healing items only autoactivate on lethal damage
|
|
if ( !bBIGPOWERUP && (Owner.Health-damage <= 0) )
|
|
return;
|
|
else if ( bBIGPOWERUP && (Owner.Health-damage > 0) )
|
|
return;
|
|
if ( (swwm_strictuntouchable == 1) && Owner.player )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.tookdamage[Owner.PlayerNumber()] = true;
|
|
}
|
|
if ( ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
|
|
int tgive = 0;
|
|
bool acc = CVar.GetCVar('swwm_accdamage',players[consoleplayer]).GetBool();
|
|
bool morethanonce = false;
|
|
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(morethanonce);
|
|
morethanonce = true;
|
|
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";
|
|
Inventory.PickupFlash "SWWMBluePickupFlash";
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|
|
|
|
// Base casing classes
|
|
Class SWWMCasing : Actor abstract
|
|
{
|
|
SWWMCasing prevcasing, nextcasing;
|
|
bool killme;
|
|
int 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";
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
heat = 1.0;
|
|
SWWMHandler.QueueCasing(self);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
SWWMHandler.DeQueueCasing(self);
|
|
Super.OnDestroy();
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( killme ) A_FadeOut(.01);
|
|
if ( waterlevel > 0 )
|
|
{
|
|
vel.xy *= .98;
|
|
anglevel *= .98;
|
|
pitchvel *= .98;
|
|
}
|
|
if ( heat <= 0 ) return;
|
|
let s = Spawn("SWWMSmallSmoke",pos);
|
|
s.alpha *= heat;
|
|
heat -= 0.05;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
angle += anglevel;
|
|
pitch += pitchvel;
|
|
}
|
|
Loop;
|
|
Bounce:
|
|
XZW1 A 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:
|
|
XZW1 B -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;
|
|
+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();
|
|
SWWMUtility.DoExplosion(self,special1,40000,120,60,ignoreme:target);
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class BigPunchImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+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("SWWMHalfSmoke",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("SWWMHalfSmoke",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
|
|
{
|
|
Radius .1;
|
|
Height 0.;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
|
|
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);
|
|
SetOrigin(origin,false);
|
|
// check for projectiles to deflect
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( !((a.bMISSILE && !a.IsZeroDamage() && (a.target != master)) || a.bSKULLFLY) || a.bTHRUACTORS || (level.Vec3Diff(a.pos,pos).length() > 80) ) continue;
|
|
Vector3 vdir = a.vel;
|
|
Vector3 dir = level.Vec3Diff(master.Vec2OffsetZ(0,0,pos.z),a.pos).unit();
|
|
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();
|
|
}
|
|
}
|
|
|
|
Class UseList
|
|
{
|
|
Line hitline;
|
|
int hitside, hitpart;
|
|
Actor hitactor;
|
|
Vector3 pos;
|
|
}
|
|
|
|
Class UseLineTracer : LineTracer
|
|
{
|
|
Array<UseList> uses;
|
|
|
|
static play bool TangibleLine( UseList u )
|
|
{
|
|
if ( u.hitpart != TIER_MIDDLE ) return true; // lower/upper/ffloor
|
|
Line l = u.HitLine;
|
|
if ( !l.sidedef[1] ) return true; // onesided line
|
|
Side s = l.sidedef[u.hitside];
|
|
if ( s.GetTexture(1).IsNull() ) return false; // no midtex
|
|
double ofs = s.GetTextureYOffset(1);
|
|
Vector2 siz = TexMan.GetScaledSize(s.GetTexture(1));
|
|
Vector2 tofs = TexMan.GetScaledOffset(s.GetTexture(1));
|
|
ofs += tofs.y;
|
|
ofs *= s.GetTextureYScale(1);
|
|
siz.y *= s.GetTextureYScale(1);
|
|
SecPlane ceil, flor;
|
|
if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p))
|
|
&& (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) )
|
|
flor = l.frontsector.floorplane;
|
|
else flor = l.backsector.floorplane;
|
|
if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p))
|
|
&& (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) )
|
|
ceil = l.frontsector.ceilingplane;
|
|
else ceil = l.backsector.ceilingplane;
|
|
double ceilpoint = max(ceil.ZatPoint(l.v1.p),ceil.ZatPoint(l.v2.p));
|
|
double florpoint = min(flor.ZatPoint(l.v1.p),flor.ZatPoint(l.v2.p));
|
|
if ( l.flags&Line.ML_DONTPEGBOTTOM )
|
|
{
|
|
if ( u.pos.z > florpoint+ofs+siz.y ) return false;
|
|
if ( u.pos.z < florpoint+ofs ) return false;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( u.pos.z > ceilpoint+ofs ) return false;
|
|
if ( u.pos.z < (ceilpoint+ofs)-siz.y ) return false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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.hitpart = Results.FFloor?TIER_FFLOOR:Results.Tier;
|
|
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;
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMCrosshairTracer : LineTracer
|
|
{
|
|
Actor ignoreme;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
|
|
return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
// Base class for all SWWM Weapons
|
|
Class SWWMWeapon : Weapon abstract
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
|
|
bool wasused;
|
|
private int SWeaponFlags;
|
|
Actor pfield; // instance of parry field for current melee attack
|
|
bool wallponch; // is punching a wall (for activation checks)
|
|
|
|
Class<Ammo> dropammotype;
|
|
Class<Weapon> SwapWeapon; // player can only own either this weapon or the SwapWeapon, if defined
|
|
|
|
Property DropAmmoType : dropammotype;
|
|
Property SwapWeapon : SwapWeapon;
|
|
|
|
FlagDef NoFirstGive : SWeaponFlags, 0; // don't give ammo on first pickup (for weapons with a clip count)
|
|
FlagDef HideInMenu : SWeaponFlags, 1; // don't show in inventory menu (usually for sister weapons)
|
|
|
|
transient CVar phair; // use custom precise crosshair
|
|
transient ui CVar ch_img, ch_siz, ch_grow, ch_col, ch_hp, ch_on, ch_force;
|
|
ui SWWMCrosshairTracer ctr;
|
|
ui Vector3 cpos;
|
|
ui Color ccol;
|
|
ui Vector2 lagvpos;
|
|
|
|
override void Touch( Actor toucher )
|
|
{
|
|
// cannot pick up swapweapon unless explicitly pressing use
|
|
if ( swwm_swapweapons && SwapWeapon && toucher.FindInventory(SwapWeapon) )
|
|
{
|
|
if ( toucher.CheckLocalView() )
|
|
{
|
|
String t2 = GetDefaultByType(SwapWeapon).GetTag();
|
|
Console.MidPrint(SmallFont,String.Format(StringTable.Localize("$SWWM_SWAPWEAPON"),t2,GetTag()));
|
|
}
|
|
return;
|
|
}
|
|
Super.Touch(toucher);
|
|
}
|
|
// allow pickup by use + swap weapon support
|
|
override bool Used( Actor user )
|
|
{
|
|
Vector3 itempos = Vec3Offset(0,0,Height/2),
|
|
userpos = user.Vec2OffsetZ(0,0,user.player.viewz);
|
|
// test vertical range
|
|
Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2));
|
|
double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2);
|
|
if ( abs(diff.z) > rang ) return false;
|
|
// if the toucher owns our SwapWeapon, drop it before picking us up
|
|
bool swapto = false;
|
|
if ( swwm_swapweapons && SwapWeapon )
|
|
{
|
|
let sw = user.FindInventory(SwapWeapon);
|
|
if ( sw )
|
|
{
|
|
if ( Weapon(sw) == user.player.ReadyWeapon )
|
|
swapto = true;
|
|
user.DropInventory(sw);
|
|
// don't autoswitch just yet (hacky)
|
|
if ( swapto )
|
|
{
|
|
user.player.ReadyWeapon = null;
|
|
user.player.PendingWeapon = WP_NOCHANGE;
|
|
}
|
|
}
|
|
}
|
|
Touch(user);
|
|
// we got picked up
|
|
if ( bDestroyed || Owner || !bSPECIAL )
|
|
{
|
|
// autoswitch to us if we got swapped
|
|
if ( swapto ) user.A_SelectWeapon(GetClass());
|
|
Vector3 tracedir = level.Vec3Diff(userpos,itempos);
|
|
double dist = tracedir.length();
|
|
tracedir /= dist;
|
|
let cf = new("CrossLineFinder");
|
|
cf.Trace(userpos,level.PointInSector(userpos.xy),tracedir,dist,0);
|
|
// trigger all player cross lines found between user and item
|
|
for ( int i=0; i<cf.clines.Size(); i++ )
|
|
cf.clines[i].Activate(user,cf.csides[i],SPAC_Cross);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !phair ) phair = CVar.GetCVar('swwm_precisecrosshair',players[consoleplayer]);
|
|
// force custom crosshair
|
|
if ( phair.GetBool() ) crosshair = 99;
|
|
else crosshair = 0;
|
|
}
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
// can't hold both weapons at once
|
|
if ( swwm_swapweapons && SwapWeapon && (item.GetClass() == SwapWeapon) )
|
|
return true;
|
|
if ( (GetClass() == item.GetClass()) && !item.ShouldStay() )
|
|
{
|
|
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()
|
|
{
|
|
if ( !Owner ) return;
|
|
[cpos, ccol] = TraceForCrosshair();
|
|
// avoid jumpy switching
|
|
if ( Owner.player.PendingWeapon is 'SWWMWeapon' )
|
|
{
|
|
SWWMWeapon(Owner.player.PendingWeapon).cpos = cpos;
|
|
SWWMWeapon(Owner.player.PendingWeapon).lagvpos = lagvpos;
|
|
}
|
|
}
|
|
// extra drawing, usually scopes
|
|
virtual ui void RenderUnderlay( RenderEvent e )
|
|
{
|
|
// draw custom crosshair
|
|
if ( automapactive || !(players[consoleplayer].Camera is 'PlayerPawn') ) return;
|
|
if ( !phair || !phair.GetBool() ) return;
|
|
if ( !ch_on ) ch_on = CVar.GetCVar('crosshairon',players[consoleplayer]);
|
|
if ( !ch_on.GetBool() ) return;
|
|
if ( !ch_force ) ch_force = CVar.GetCVar('crosshairforce',players[consoleplayer]);
|
|
if ( ch_force.GetBool() ) return;
|
|
let sb = SWWMStatusBar(StatusBar);
|
|
if ( !sb ) return;
|
|
sb.viewport.FromHud();
|
|
sb.proj.CacheResolution();
|
|
sb.proj.CacheFov(players[consoleplayer].fov);
|
|
sb.proj.OrientForRenderUnderlay(e);
|
|
sb.proj.BeginProjection();
|
|
Vector3 tdir = level.Vec3Diff(e.ViewPos,cpos);
|
|
// project
|
|
sb.proj.ProjectWorldPos(e.ViewPos+tdir);
|
|
Vector2 vpos;
|
|
if ( !sb.proj.IsInFront() ) return;
|
|
Vector2 npos = sb.proj.ProjectToNormal();
|
|
vpos = sb.viewport.SceneToWindow(npos);
|
|
if ( lagvpos == (0,0) ) lagvpos = vpos;
|
|
else lagvpos = lagvpos*.7+vpos*.3; // this will be fucky depending on the FPS, but oh well
|
|
if ( !ch_img ) ch_img = CVar.GetCVar('crosshair',players[consoleplayer]);
|
|
int cnum = abs(ch_img.GetInt());
|
|
if ( !cnum ) return;
|
|
String tn = String.Format("XHAIR%s%d",(Screen.GetWidth()<640)?"S":"B",cnum);
|
|
TextureID ctex = TexMan.CheckForTexture(tn,TexMan.Type_MiscPatch);
|
|
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture(String.Format("XHAIR%s1",(Screen.GetWidth()<640)?"S":"B"),TexMan.Type_MiscPatch);
|
|
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture("XHAIRS1",TexMan.Type_MiscPatch);
|
|
Vector2 ts = TexMan.GetScaledSize(ctex);
|
|
if ( !ch_siz ) ch_siz = CVar.GetCVar('crosshairscale',players[consoleplayer]);
|
|
double cs = ch_siz.GetFloat();
|
|
double sz = 1.;
|
|
if ( cs > 0. ) sz = Screen.GetHeight()*cs/200.;
|
|
if ( !ch_grow ) ch_grow = CVar.GetCVar('crosshairgrow',players[consoleplayer]);
|
|
if ( ch_grow.GetBool() ) sz *= sb.CrosshairSize;
|
|
Screen.DrawTexture(ctex,false,int(lagvpos.x),int(lagvpos.y),DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol);
|
|
}
|
|
ui Vector3, Color TraceForCrosshair()
|
|
{
|
|
if ( !ctr ) ctr = new("SWWMCrosshairTracer");
|
|
ctr.ignoreme = Owner;
|
|
Vector3 x, y, z, ofs;
|
|
double s;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
|
|
ofs = GetTraceOffset();
|
|
Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),ofs.x*x+ofs.y*y+ofs.z*z);
|
|
ctr.Trace(origin,level.PointInSector(origin.xy),x,10000.,0);
|
|
if ( !ch_col ) ch_col = CVar.GetCVar('crosshaircolor',players[consoleplayer]);
|
|
Color col = ch_col.GetInt();
|
|
if ( !ch_hp ) ch_hp = CVar.GetCVar('crosshairhealth',players[consoleplayer]);
|
|
if ( ch_hp.GetInt() >= 2 )
|
|
{
|
|
int hp = Clamp(Owner.Health,0,200);
|
|
double sat = (hp<150)?1.:(1.-(hp-150)/100.);
|
|
Vector3 rgb = SWWMUtility.HSVtoRGB((hp/300.,sat,1.));
|
|
col = Color(int(rgb.x*255),int(rgb.y*255),int(rgb.z*255));
|
|
}
|
|
else if ( ch_hp.GetInt() == 1 )
|
|
{
|
|
double hp = Clamp(Owner.Health,0,100)/100.;
|
|
if ( hp <= 0 ) col = Color(255,0,0);
|
|
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
|
|
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
|
|
else col = Color(0,255,0);
|
|
}
|
|
else if ( (ctr.Results.HitType == TRACE_HitActor) && ctr.Results.HitActor.bSHOOTABLE )
|
|
{
|
|
// show target health, rather than our own
|
|
double hp = ctr.Results.HitActor.Health/double(ctr.Results.HitActor.GetSpawnHealth());
|
|
if ( hp <= 0 ) col = Color(255,0,0);
|
|
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
|
|
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
|
|
else col = Color(0,255,0);
|
|
}
|
|
if ( ctr.Results.HitType == TRACE_HitNone ) return level.Vec3Offset(origin,x*10000.), col;
|
|
else return ctr.Results.HitPos, col;
|
|
}
|
|
// where the trace is coming from relative to eyes
|
|
virtual clearscope Vector3 GetTraceOffset() const
|
|
{
|
|
return (0.,0.,0.);
|
|
}
|
|
// 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);
|
|
if ( !psp ) return;
|
|
ResetPSprite(psp);
|
|
psp.y = WEAPONTOP;
|
|
// do not jump to ready state here, the weapon should do that
|
|
// directly once it finishes playing its select animation
|
|
}
|
|
action void A_FullLower()
|
|
{
|
|
if ( !player ) return;
|
|
if ( !player.ReadyWeapon )
|
|
{
|
|
player.mo.BringUpWeapon();
|
|
return;
|
|
}
|
|
let psp = player.GetPSprite(PSP_WEAPON);
|
|
if ( !psp ) return;
|
|
psp.y = WEAPONBOTTOM;
|
|
ResetPSprite(psp);
|
|
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.);
|
|
SWWMUtility.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 && !d.HitActor.bDORMANT && (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(swwm_uncapalert?0:300);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
action void A_Melee( int dmg = 40, String hitsound = "", double rangemul = 1. )
|
|
{
|
|
Vector3 origin = Vec3Offset(0,0,player.viewheight);
|
|
Vector3 dir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch));
|
|
// check for usables
|
|
let ut = new("UseLineTracer");
|
|
ut.uses.Clear();
|
|
ut.Trace(origin,CurSector,dir,DEFMELEERANGE*rangemul,0);
|
|
invoker.wallponch = true;
|
|
for ( int i=0; i<ut.uses.Size(); i++ )
|
|
{
|
|
if ( ut.uses[i].hitactor )
|
|
{
|
|
// punching is not greeting/patting (that'd be weird)
|
|
if ( (ut.uses[i].hitactor == self) || (ut.uses[i].hitactor is 'Demolitionist')
|
|
|| (ut.uses[i].hitactor is 'HeadpatTracker') ) continue;
|
|
if ( ut.uses[i].hitactor.Used(self) ) break;
|
|
}
|
|
else if ( ut.uses[i].hitline && UseLineTracer.TangibleLine(ut.uses[i]) )
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
invoker.wallponch = false;
|
|
// check for shootables
|
|
SWWMBulletTrail.DoTrail(self,origin,dir,DEFMELEERANGE*rangemul,0);
|
|
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) )
|
|
return;
|
|
}
|
|
// 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 ( d.HitType == TRACE_HitFloor ) p.CheckSplash(40);
|
|
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(swwm_uncapalert?0:100);
|
|
if ( raging ) raging.DoHitFX();
|
|
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,raging?(dmg*8):dmg,self,d.HitDir,d.HitLocation.z);
|
|
}
|
|
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 bool SpecialDropAction( Actor dropper )
|
|
{
|
|
if ( swwm_enemydrops > 0 ) return false;
|
|
else if ( swwm_enemydrops == 0 )
|
|
{
|
|
// drop our corresponding ammo
|
|
if ( !DropAmmoType ) return true;
|
|
let a = Inventory(Spawn(DropAmmoType,pos,ALLOW_REPLACE));
|
|
if ( !a ) return true;
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
if ( !(level.compatflags&COMPATF_NOTOSSDROPS) )
|
|
a.TossItem();
|
|
if ( a is 'Ammo' )
|
|
a.ModifyDropAmount(Ammo(a).DropAmount);
|
|
a.bTOSSED = true;
|
|
if ( a.SpecialDropAction(dropper) )
|
|
a.Destroy();
|
|
return true;
|
|
}
|
|
// no weapon drops from enemies
|
|
return true;
|
|
}
|
|
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.SlotPriority 1.;
|
|
Inventory.RestrictedTo "Demolitionist";
|
|
Inventory.PickupFlash "SWWMRedPickupFlash";
|
|
+INVENTORY.IGNORESKILL;
|
|
+WEAPON.NOALERT;
|
|
+WEAPON.NODEATHINPUT;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|