Use it sparingly in places where it should have been needed (Hellblazer and Biospark pre/post fire, Hammer alt charge, MR overpressure charge). Also, replace all instances of A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH) with it, since that was basically the same thing but with extra steps (and with the side effect of potentially resetting psprite offsets).
956 lines
28 KiB
Text
956 lines
28 KiB
Text
// Base class for all SWWM Weapons
|
|
Class SWWMWeapon : Weapon abstract
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUnrealStyleDrop;
|
|
|
|
bool wasused;
|
|
bool bUsePickup;
|
|
private int SWeaponFlags;
|
|
meta String tooltip, getline;
|
|
bool tooltipsent;
|
|
meta Class<Actor> dropammotype;
|
|
int dropamount;
|
|
bool bInitialized;
|
|
meta double bobfactor_ang, bobfactor_vec;
|
|
|
|
Property Tooltip : tooltip;
|
|
Property GetLine : getline;
|
|
Property DropAmmoType : dropammotype;
|
|
Property BobFactor : bobfactor_ang, bobfactor_vec;
|
|
|
|
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)
|
|
FlagDef NoSwapWeapon : SWeaponFlags, 2; // weapon is not affected by slot swapping
|
|
FlagDef HasScrTex : SWeaponFlags, 3; // weapon model has a scripted texture (calls RenderTexture() from Event Handler)
|
|
FlagDef SwappedTo : SWeaponFlags, 4; // this weapon was swapped to on pickup (so it won't respawn)
|
|
|
|
int oldtagcolor;
|
|
|
|
double bvstr, bastr;
|
|
Vector3 bvdir;
|
|
int bvtics, batics;
|
|
|
|
bool IsSwapWeapon( Inventory i ) const
|
|
{
|
|
if ( bNoSwapWeapon || (i.GetClass() == GetClass()) ) return false;
|
|
let w = SWWMWeapon(i);
|
|
if ( w && !w.bNoSwapWeapon && (SlotNumber != -1) && (w.SlotNumber == SlotNumber) )
|
|
return true;
|
|
let wg = SWWMDualWeaponGiver(i);
|
|
if ( wg )
|
|
{
|
|
let w = wg.giveme[0];
|
|
if ( w && !w.bNoSwapWeapon && (SlotNumber != -1) & (w.SlotNumber == SlotNumber) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
SWWMWeapon HasSwapWeapon( Actor other ) const
|
|
{
|
|
if ( bNoSwapWeapon ) return null;
|
|
for ( Inventory i=other.inv; i; i=i.inv )
|
|
{
|
|
if ( IsSwapWeapon(i) )
|
|
return SWWMWeapon(i);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// temporarily needed to ensure unimplemented weapons can still activate their pickup specials, to avoid softlocks
|
|
private void OnTouchSpecials( Actor toucher )
|
|
{
|
|
DoPickupSpecial(toucher);
|
|
if ( bCountItem )
|
|
{
|
|
if ( toucher.player ) toucher.player.itemcount++;
|
|
level.found_items++;
|
|
bCountItem = false;
|
|
}
|
|
if ( bCountSecret )
|
|
{
|
|
if ( toucher.player ) toucher.player.mo.GiveSecret(true,true);
|
|
else toucher.GiveSecret(true,true);
|
|
bCountSecret = false;
|
|
}
|
|
}
|
|
|
|
override void Touch( Actor toucher )
|
|
{
|
|
// show message about unimplemented weapons
|
|
State ReadyState = FindState('Ready');
|
|
if ( !ReadyState || !ReadyState.ValidateSpriteFrame() )
|
|
{
|
|
if ( toucher.CheckLocalView() )
|
|
Console.MidPrint(SmallFont,String.Format(StringTable.Localize(SWWMUtility.SellFemaleItem(self,"SWWM_TODOWEAPON_FEM")?"$SWWM_TODOWEAPON_FEM":"$SWWM_TODOWEAPON"),GetTag()));
|
|
OnTouchSpecials(toucher);
|
|
return;
|
|
}
|
|
// show prompt to swap weapon, and prevent normal pickup
|
|
SWWMWeapon sw;
|
|
if ( bSPECIAL && swwm_swapweapons && (sw = HasSwapWeapon(toucher)) )
|
|
{
|
|
if ( toucher.CheckLocalView() )
|
|
{
|
|
// use sisterweapon tag for dual wield (slot 2 weapons)
|
|
if ( sw.SisterWeapon && (sw.Amount > 1) )
|
|
Console.MidPrint(SmallFont,String.Format(StringTable.Localize("$SWWM_SWAPWEAPON"),sw.SisterWeapon.GetTag(),GetTag()));
|
|
else Console.MidPrint(SmallFont,String.Format(StringTable.Localize("$SWWM_SWAPWEAPON"),sw.GetTag(),GetTag()));
|
|
}
|
|
return;
|
|
}
|
|
// explicit use-to pickup, function must be called from Used() virtual
|
|
if ( toucher.player && swwm_usetopickup && !bUsePickup )
|
|
return;
|
|
Super.Touch(toucher);
|
|
}
|
|
// allow pickup by use + swap weapon support
|
|
override bool Used( Actor user )
|
|
{
|
|
// can't pick up
|
|
if ( !bSPECIAL ) return false;
|
|
// no use through melee
|
|
if ( (user.player.ReadyWeapon is 'SWWMWeapon') && SWWMWeapon(user.player.ReadyWeapon).wallponch )
|
|
return false;
|
|
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;
|
|
SWWMWeapon sw;
|
|
State ReadyState = FindState('Ready');
|
|
if ( swwm_swapweapons && (sw = HasSwapWeapon(user)) && (user.player.WeaponState&WF_WEAPONSWITCHOK) && !(user.player.WeaponState&WF_DISABLESWITCH) && ReadyState && ReadyState.ValidateSpriteFrame() )
|
|
{
|
|
// special case, otherwise candy gun won't drop itself
|
|
if ( sw is 'CandyGun' ) CandyGun(sw).swapdrop = true;
|
|
if ( (sw == user.player.ReadyWeapon) || (sw.SisterWeapon && (sw.SisterWeapon == user.player.ReadyWeapon)) )
|
|
swapto = true;
|
|
// don't autoswitch just yet (hacky)
|
|
if ( swapto )
|
|
{
|
|
user.player.ReadyWeapon = null;
|
|
user.player.PendingWeapon = WP_NOCHANGE;
|
|
}
|
|
int ngun = sw.Amount;
|
|
if ( ngun == 2 )
|
|
{
|
|
// create a dual giver
|
|
let dg = SWWMDualWeaponGiver(Spawn("SWWMDualWeaponGiver",pos));
|
|
dg.bDROPPED = bDROPPED; // inherit drop flag
|
|
dg.angle = angle;
|
|
dg.vel = vel;
|
|
dg.FloatBobPhase = FloatBobPhase;
|
|
// transfer both guns
|
|
dg.giveme[0] = SWWMWeapon(sw.CreateTossable(1));
|
|
dg.giveme[0].AttachToOwner(dg);
|
|
dg.giveme[1] = SWWMWeapon(sw.CreateTossable(1));
|
|
dg.giveme[1].AttachToOwner(dg);
|
|
dg.SetPickupState();
|
|
dg.bSPECIAL = false;
|
|
dg.DropTime = 30;
|
|
}
|
|
else
|
|
{
|
|
// swap in-place
|
|
let d = user.DropInventory(sw);
|
|
d.bDROPPED = bDROPPED; // inherit drop flag
|
|
d.SetOrigin(pos,false);
|
|
d.angle = angle;
|
|
d.vel = vel;
|
|
d.FloatBobPhase = FloatBobPhase;
|
|
d.bSPECIAL = false;
|
|
d.DropTime = 30;
|
|
}
|
|
}
|
|
bUsePickup = true;
|
|
bSWAPPEDTO = true;
|
|
Touch(user);
|
|
bUsePickup = false;
|
|
// we got picked up
|
|
if ( bDestroyed || Owner || !bSPECIAL )
|
|
{
|
|
bSWAPPEDTO = false; // clear this flag
|
|
// 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,ignoreallactors:true);
|
|
// 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 int, int CheckAddToSlots()
|
|
{
|
|
if ( (GetReplacement(GetClass()) == GetClass()) && !bPowered_Up )
|
|
{
|
|
if ( swwm_singlefirst && SisterWeaponType )
|
|
{
|
|
let sw = GetDefaultByType(SisterWeaponType);
|
|
return sw.SlotNumber, int(sw.SlotPriority*65536);
|
|
}
|
|
return SlotNumber, int(SlotPriority*65536);
|
|
}
|
|
return -1, 0;
|
|
}
|
|
|
|
action void A_BumpFOV( double factor )
|
|
{
|
|
if ( !(self is 'Demolitionist') ) return;
|
|
Demolitionist(self).lastbump *= factor;
|
|
}
|
|
|
|
action void A_BumpView( double factor, Vector3 dir = (0,0,0), int tics = 0 )
|
|
{
|
|
if ( !(self is 'Demolitionist') ) return;
|
|
Demolitionist(self).BumpView(factor,dir);
|
|
if ( tics > 1 )
|
|
{
|
|
invoker.bvstr = factor;
|
|
invoker.bvdir = dir;
|
|
invoker.bvtics = tics-1;
|
|
}
|
|
}
|
|
action void A_BumpAngle( double factor, int tics = 0 )
|
|
{
|
|
if ( !(self is 'Demolitionist') ) return;
|
|
Demolitionist(self).bumpangle += factor;
|
|
if ( tics > 1 )
|
|
{
|
|
invoker.bastr = factor;
|
|
invoker.batics = tics-1;
|
|
}
|
|
}
|
|
|
|
// subtracts given ammo from price, drops excess
|
|
virtual bool PickupForAmmoSWWM( SWWMWeapon ownedWeapon )
|
|
{
|
|
// save time, always return false if we don't use ammo
|
|
if ( !ownedWeapon.Ammo1 && !ownedWeapon.Ammo2 ) return false;
|
|
bool gotstuff = false;
|
|
int oldamount1 = 0, oldamount2 = 0;
|
|
if ( ownedWeapon.Ammo1 ) oldamount1 = ownedWeapon.Ammo1.Amount;
|
|
if ( ownedWeapon.Ammo2 ) oldamount2 = ownedWeapon.Ammo2.Amount;
|
|
if ( AmmoGive1 > 0 ) gotstuff = AddExistingAmmo(ownedWeapon.Ammo1,AmmoGive1);
|
|
if ( AmmoGive2 > 0 ) gotstuff |= AddExistingAmmo(ownedWeapon.Ammo2,AmmoGive2);
|
|
let Owner = ownedWeapon.Owner;
|
|
if ( gotstuff && Owner && Owner.player )
|
|
{
|
|
if ( ownedWeapon.Ammo1 && (oldamount1 == 0) )
|
|
PlayerPawn(Owner).CheckWeaponSwitch(ownedWeapon.Ammo1.GetClass());
|
|
else if ( ownedWeapon.Ammo2 && (oldamount2 == 0) )
|
|
PlayerPawn(Owner).CheckWeaponSwitch(ownedWeapon.Ammo2.GetClass());
|
|
}
|
|
if ( ownedWeapon.Ammo1 )
|
|
{
|
|
// subtract price of ammo we don't give
|
|
int ammonotgiven = default.AmmoGive1-AmmoGive1;
|
|
if ( ammonotgiven > 0 ) Stamina -= int(abs(ownedWeapon.Ammo1.Stamina)*(1.+.75*(ammonotgiven-1)));
|
|
// subtract price of given ammo
|
|
int ammogiven = ownedWeapon.Ammo1.Amount-oldamount1;
|
|
if ( ammogiven > 0 ) Stamina -= int(abs(ownedWeapon.Ammo1.Stamina)*(1.+.75*(ammogiven-1)));
|
|
// drop excess
|
|
int dropme = AmmoGive1-ammogiven;
|
|
if ( dropme > 0 )
|
|
{
|
|
if ( (ownedWeapon.Ammo1 is 'SWWMAmmo') && SWWMAmmo(ownedWeapon.Ammo1).MagAmmoType )
|
|
{
|
|
// can we add it as mag ammo?
|
|
MagAmmo ma = MagAmmo(Owner.FindInventory(SWWMAmmo(ownedWeapon.Ammo1).MagAmmoType));
|
|
if ( !ma )
|
|
{
|
|
ma = MagAmmo(Spawn(SWWMAmmo(ownedWeapon.Ammo1).MagAmmoType));
|
|
ma.Amount = 0;
|
|
ma.AttachToOwner(Owner);
|
|
}
|
|
while ( ma.Amount <= (ma.MaxAmount-ma.ClipSize) )
|
|
{
|
|
ma.Amount += ma.ClipSize;
|
|
dropme--;
|
|
}
|
|
}
|
|
if ( dropme > 0 )
|
|
{
|
|
// hacky, but it works
|
|
ownedWeapon.Ammo1.CreateTossable(dropme);
|
|
ownedWeapon.Ammo1.Amount += dropme;
|
|
}
|
|
}
|
|
}
|
|
if ( ownedWeapon.Ammo2 )
|
|
{
|
|
// subtract price of ammo we don't give
|
|
int ammonotgiven = default.AmmoGive2-AmmoGive2;
|
|
if ( ammonotgiven > 0 ) Stamina -= int(abs(ownedWeapon.Ammo2.Stamina)*(1.+.75*(ammonotgiven-1)));
|
|
// subtract price of given ammo
|
|
int ammogiven = ownedWeapon.Ammo2.Amount-oldamount2;
|
|
if ( ammogiven > 0 ) Stamina -= int(abs(ownedWeapon.Ammo2.Stamina)*(1.+.75*(ammogiven-1)));
|
|
// drop excess
|
|
int dropme = AmmoGive2-ammogiven;
|
|
if ( dropme > 0 )
|
|
{
|
|
if ( (ownedWeapon.Ammo2 is 'SWWMAmmo') && SWWMAmmo(ownedWeapon.Ammo2).MagAmmoType )
|
|
{
|
|
// can we add it as mag ammo?
|
|
MagAmmo ma = MagAmmo(Owner.FindInventory(SWWMAmmo(ownedWeapon.Ammo2).MagAmmoType));
|
|
if ( !ma )
|
|
{
|
|
ma = MagAmmo(Spawn(SWWMAmmo(ownedWeapon.Ammo2).MagAmmoType));
|
|
ma.Amount = 0;
|
|
ma.AttachToOwner(Owner);
|
|
}
|
|
while ( ma.Amount <= (ma.MaxAmount-ma.ClipSize) )
|
|
{
|
|
ma.Amount += ma.ClipSize;
|
|
dropme--;
|
|
}
|
|
}
|
|
if ( dropme > 0 )
|
|
{
|
|
// hacky, but it works
|
|
ownedWeapon.Ammo2.CreateTossable(dropme);
|
|
ownedWeapon.Ammo2.Amount += dropme;
|
|
}
|
|
}
|
|
}
|
|
return gotstuff;
|
|
}
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
// can't hold both weapons at once
|
|
if ( swwm_swapweapons && IsSwapWeapon(item) )
|
|
return true;
|
|
if ( (GetClass() == item.GetClass()) && !item.ShouldStay() )
|
|
{
|
|
if ( SWWMWeapon(item).PickupForAmmoSWWM(self) )
|
|
item.bPickupGood = true;
|
|
if ( !deathmatch && (Amount+item.Amount > MaxAmount) && (item.Stamina != 0) )
|
|
{
|
|
// sell excess
|
|
if ( Owner.player )
|
|
{
|
|
int sellprice = abs(item.Stamina)/2;
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player == players[consoleplayer] )
|
|
{
|
|
SWWMScoreObj.SpawnAtActorBunch(sellprice,Owner);
|
|
Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),GetTag(),sellprice);
|
|
}
|
|
else Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRAREM_FEM":"$SWWM_SELLEXTRAREM"),Owner.player.GetUserName(),GetTag(),sellprice);
|
|
}
|
|
item.bPickupGood = true;
|
|
}
|
|
// reset the price in case it has to respawn
|
|
item.Stamina = item.default.Stamina;
|
|
return true;
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
override bool ShouldStay()
|
|
{
|
|
// SWWM weapons never stay unless explicitly stated
|
|
if ( !bDROPPED && (deathmatch || alwaysapplydmflags) && sv_weaponstay ) return true;
|
|
return false;
|
|
}
|
|
// we need to copy this since we can't re-use the mixin
|
|
override void Hide()
|
|
{
|
|
bSPECIAL = false;
|
|
bNOGRAVITY = true;
|
|
bINVISIBLE = true;
|
|
SetState(FindState("HideDoomish"));
|
|
tics = 1050;
|
|
if ( self.bBIGPOWERUP || SWWMUtility.IsVIPItem(self) )
|
|
tics += 1050;
|
|
if ( RespawnTics != 0 ) tics = RespawnTics;
|
|
if ( ShouldRespawn() )
|
|
{
|
|
Vector3 oldpos = pos;
|
|
A_RestoreSpecialPosition();
|
|
let t = Spawn("SWWMRespawnTimer",pos);
|
|
t.tracer = self;
|
|
t.special1 = tics;
|
|
t.A_SetSize(radius,height);
|
|
SetOrigin(oldpos,false);
|
|
}
|
|
}
|
|
override bool ShouldRespawn()
|
|
{
|
|
// swapped-to weapons do not respawn
|
|
if ( bSWAPPEDTO ) return false;
|
|
// always respawn in DM
|
|
if ( deathmatch && !bNEVERRESPAWN ) return true;
|
|
if ( (bBigPowerup || SWWMUtility.IsVIPItem(self)) && !sv_respawnsuper ) return false;
|
|
if ( bNEVERRESPAWN ) return false;
|
|
return (sv_itemrespawn||bALWAYSRESPAWN);
|
|
}
|
|
protected Ammo AddAmmoSWWM( Actor other, Class<Ammo> ammotype, int amount )
|
|
{
|
|
// no weirdass factors or anything involved in this one
|
|
// just simple and straightforward
|
|
if ( !ammotype ) return null;
|
|
let amo = Ammo(other.FindInventory(ammotype));
|
|
if ( !amo )
|
|
{
|
|
amo = Ammo(Spawn(ammotype));
|
|
amo.amount = 0;
|
|
amo.AttachToOwner(other);
|
|
}
|
|
amo.amount += amount;
|
|
if ( amo.amount > amo.maxamount && !sv_unlimited_pickup )
|
|
amo.amount = amo.maxamount;
|
|
return amo;
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Inventory.AttachToOwner(other);
|
|
Ammo1 = AddAmmoSWWM(Owner,AmmoType1,bNoFirstGive?0:AmmoGive1);
|
|
Ammo2 = AddAmmoSWWM(Owner,AmmoType2,bNoFirstGive?0:AmmoGive2);
|
|
SisterWeapon = AddWeapon(SisterWeaponType);
|
|
if ( !bInitialized )
|
|
{
|
|
InitializeWeapon();
|
|
bInitialized = true;
|
|
}
|
|
if ( Owner.player )
|
|
{
|
|
if ( !Owner.player.GetNeverSwitch() && !bNo_Auto_Switch && ReportHUDAmmo() ) // hey, as long as it works
|
|
{
|
|
// if the player's current/next weapon is a gesture, set ourselves as the former weapon, to avoid breaking gesture sequences
|
|
if ( Owner.player.ReadyWeapon is 'SWWMGesture' )
|
|
{
|
|
let g = SWWMGesture(Owner.player.ReadyWeapon);
|
|
g.formerweapon = self;
|
|
}
|
|
else if ( Owner.player.PendingWeapon is 'SWWMGesture' )
|
|
{
|
|
let g = SWWMGesture(Owner.player.PendingWeapon);
|
|
g.formerweapon = self;
|
|
}
|
|
else if ( Owner.player.ReadyWeapon is 'SWWMItemGesture' )
|
|
{
|
|
let ig = SWWMItemGesture(Owner.player.ReadyWeapon);
|
|
ig.gest.formerweapon = self;
|
|
}
|
|
else if ( Owner.player.PendingWeapon is 'SWWMItemGesture' )
|
|
{
|
|
let ig = SWWMItemGesture(Owner.player.PendingWeapon);
|
|
ig.gest.formerweapon = self;
|
|
}
|
|
else Owner.player.PendingWeapon = self;
|
|
}
|
|
if ( Owner.player.mo == players[consoleplayer].camera )
|
|
StatusBar.ReceivedWeapon(self);
|
|
}
|
|
GivenAsMorphWeapon = false;
|
|
bSwappedTo = false;
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
// we can simplify this
|
|
if ( !Owner.player || (Owner.player.ReadyWeapon == self) )
|
|
return false;
|
|
// if the player's current/next weapon is a gesture, set ourselves as the former weapon, to avoid breaking gesture sequences
|
|
if ( Owner.player.ReadyWeapon is 'SWWMGesture' )
|
|
{
|
|
let g = SWWMGesture(Owner.player.ReadyWeapon);
|
|
g.formerweapon = self;
|
|
}
|
|
else if ( Owner.player.PendingWeapon is 'SWWMGesture' )
|
|
{
|
|
let g = SWWMGesture(Owner.player.PendingWeapon);
|
|
g.formerweapon = self;
|
|
}
|
|
else if ( Owner.player.ReadyWeapon is 'SWWMItemGesture' )
|
|
{
|
|
let ig = SWWMItemGesture(Owner.player.ReadyWeapon);
|
|
ig.gest.formerweapon = self;
|
|
}
|
|
else if ( Owner.player.PendingWeapon is 'SWWMItemGesture' )
|
|
{
|
|
let ig = SWWMItemGesture(Owner.player.PendingWeapon);
|
|
ig.gest.formerweapon = self;
|
|
}
|
|
else Owner.player.PendingWeapon = self;
|
|
return false;
|
|
}
|
|
override void OwnerDied()
|
|
{
|
|
ClearBufferedAmmo();
|
|
if ( Owner.player && (Owner.player.ReadyWeapon == self) )
|
|
{
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA2);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA3);
|
|
}
|
|
A_ClearRefire();
|
|
Super.OwnerDied();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
ClearBufferedAmmo();
|
|
Super.Travelled();
|
|
}
|
|
override void DetachFromOwner()
|
|
{
|
|
ClearBufferedAmmo();
|
|
Super.DetachFromOwner();
|
|
}
|
|
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);
|
|
}
|
|
// override in subclasses to perform first-time setup on a newly obtained weapon
|
|
virtual void InitializeWeapon()
|
|
{
|
|
}
|
|
// draw ammo on hud above weapon box
|
|
virtual ui void DrawWeapon( double TicFrac, double bx, double by, double hs, Vector2 ss )
|
|
{
|
|
}
|
|
// extra drawing, usually scopes
|
|
virtual ui void RenderUnderlay( RenderEvent e )
|
|
{
|
|
}
|
|
// HUD-side ticking
|
|
virtual ui void HudTick()
|
|
{
|
|
}
|
|
// updating a scripted texture in the model
|
|
virtual ui void RenderTexture( RenderEvent e )
|
|
{
|
|
}
|
|
// update color tags on first person model
|
|
void UpdateTags( int idx, String colname )
|
|
{
|
|
if ( idx != oldtagcolor )
|
|
SetTags(colname);
|
|
oldtagcolor = idx;
|
|
}
|
|
// should be overridable in case indices aren't the same
|
|
// (or there's dual-wielding)
|
|
virtual void SetTags( String colname )
|
|
{
|
|
A_ChangeModel("",1,"","",0,"models","DemoTags"..colname..".png",CMDL_USESURFACESKIN,-1);
|
|
}
|
|
// third-person animations for player
|
|
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( bool bFast = false )
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) )
|
|
{
|
|
if ( bFast ) demo.PlayFastReload();
|
|
else 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();
|
|
}
|
|
// quick 'n dirty function to enable bobbing for the current state
|
|
// cleaner than using A_WeaponReady(WRF_NOFIRE|WRF_NOSWITCH), at least
|
|
action void A_CanBob( bool toggle = true )
|
|
{
|
|
if ( !player ) return;
|
|
if ( toggle ) player.weaponstate |= WF_WEAPONBOBBING;
|
|
else player.weaponstate &= ~WF_WEAPONBOBBING;
|
|
}
|
|
override void PlayUpSound( Actor origin )
|
|
{
|
|
if ( UpSound ) origin.A_StartSound(UpSound,CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( bvtics > 0 )
|
|
{
|
|
if ( Demolitionist(Owner) && Owner.player && (Owner.player.ReadyWeapon == self) )
|
|
Demolitionist(Owner).BumpView(bvstr,bvdir);
|
|
bvtics--;
|
|
}
|
|
if ( batics > 0 )
|
|
{
|
|
if ( Demolitionist(Owner) && Owner.player && (Owner.player.ReadyWeapon == self) )
|
|
Demolitionist(Owner).BumpAngle += bastr;
|
|
batics--;
|
|
}
|
|
if ( !Owner )
|
|
{
|
|
angle -= (180./64.);
|
|
return;
|
|
}
|
|
if ( !Owner.player || (Owner.player.ReadyWeapon != self) || !(Owner.player.WeaponState&WF_WEAPONSWITCHOK) || (Owner.player.WeaponState&WF_DISABLESWITCH) )
|
|
{
|
|
tooltipsent = false;
|
|
return;
|
|
}
|
|
if ( tooltipsent ) return;
|
|
tooltipsent = true;
|
|
if ( (tooltip != "") && (Owner.player == players[consoleplayer]) )
|
|
SWWMUtility.SendTooltip(GetClass());
|
|
}
|
|
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));
|
|
}
|
|
// ensure weapons dropped by enemies only give one unit of each ammo
|
|
override void ModifyDropAmount( int dropamount )
|
|
{
|
|
self.dropamount = 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 = 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 'Inventory' )
|
|
{
|
|
let i = Inventory(a);
|
|
i.ModifyDropAmount(dropamount);
|
|
i.bTOSSED = true;
|
|
if ( i.SpecialDropAction(dropper) )
|
|
i.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;
|
|
let ret = Super.CreateTossable(amt);
|
|
// reattach our glow if we became a pickup
|
|
if ( (ret == self) && (PickupFlash is 'SWWMPickupFlash') )
|
|
{
|
|
let p = Spawn(PickupFlash,pos);
|
|
p.target = self;
|
|
p.SetStateLabel("Pickup");
|
|
}
|
|
return ret;
|
|
}
|
|
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";
|
|
SWWMWeapon.BobFactor 1., .0001;
|
|
+INVENTORY.IGNORESKILL;
|
|
+WEAPON.NOALERT;
|
|
+WEAPON.NODEATHINPUT;
|
|
+FLOATBOB;
|
|
+INTERPOLATEANGLES;
|
|
+DONTGIB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|
|
|
|
// used for dual swapweapons
|
|
Class SWWMDualWeaponGiver : Inventory
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMRotatingPickup;
|
|
Mixin SWWMUnrealStyleDrop;
|
|
|
|
bool bUsePickup;
|
|
SWWMWeapon giveme[2];
|
|
|
|
Default
|
|
{
|
|
Inventory.PickupSound "misc/w_pkup";
|
|
Inventory.RestrictedTo "Demolitionist";
|
|
Inventory.PickupFlash "SWWMRedPickupFlash";
|
|
+WEAPONSPAWN;
|
|
+FLOATBOB;
|
|
+DONTGIB;
|
|
+INVENTORY.NEVERRESPAWN;
|
|
+INVENTORY.QUIET;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
|
|
SWWMWeapon HasSwapWeapon( Actor other ) const
|
|
{
|
|
if ( giveme[0].bNoSwapWeapon ) return null;
|
|
for ( Inventory i=other.inv; i; i=i.inv )
|
|
{
|
|
if ( giveme[0].IsSwapWeapon(i) )
|
|
return SWWMWeapon(i);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
override void Touch( Actor toucher )
|
|
{
|
|
// show prompt to swap weapon, and prevent normal pickup
|
|
SWWMWeapon sw;
|
|
if ( bSPECIAL && swwm_swapweapons && (sw = giveme[0].HasSwapWeapon(toucher)) )
|
|
{
|
|
if ( toucher.CheckLocalView() )
|
|
{
|
|
// use sisterweapon tag for dual wield (slot 2 weapons)
|
|
if ( sw.SisterWeapon && (sw.Amount > 1) )
|
|
Console.MidPrint(SmallFont,String.Format(StringTable.Localize("$SWWM_SWAPWEAPON"),sw.SisterWeapon.GetTag(),GetTag()));
|
|
else Console.MidPrint(SmallFont,String.Format(StringTable.Localize("$SWWM_SWAPWEAPON"),sw.GetTag(),GetTag()));
|
|
}
|
|
return;
|
|
}
|
|
// explicit use-to pickup, function must be called from Used() virtual
|
|
if ( toucher.player && swwm_usetopickup && !bUsePickup )
|
|
return;
|
|
Super.Touch(toucher);
|
|
}
|
|
|
|
override bool Used( Actor user )
|
|
{
|
|
// can't pick up
|
|
if ( !bSPECIAL ) return false;
|
|
// no use through melee
|
|
if ( (user.player.ReadyWeapon is 'SWWMWeapon') && SWWMWeapon(user.player.ReadyWeapon).wallponch )
|
|
return false;
|
|
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;
|
|
SWWMWeapon sw;
|
|
if ( swwm_swapweapons && (sw = giveme[0].HasSwapWeapon(user)) )
|
|
{
|
|
// no need for candygun check here
|
|
if ( (sw == user.player.ReadyWeapon) || (sw.SisterWeapon && (sw.SisterWeapon == user.player.ReadyWeapon)) )
|
|
swapto = true;
|
|
int ngun = sw.Amount;
|
|
if ( ngun == 2 )
|
|
{
|
|
// create a dual giver
|
|
let dg = SWWMDualWeaponGiver(Spawn("SWWMDualWeaponGiver",pos));
|
|
dg.angle = angle;
|
|
dg.vel = vel;
|
|
dg.FloatBobPhase = FloatBobPhase;
|
|
// transfer both guns
|
|
dg.giveme[0] = SWWMWeapon(sw.CreateTossable(1));
|
|
dg.giveme[0].AttachToOwner(dg);
|
|
dg.giveme[1] = SWWMWeapon(sw.CreateTossable(1));
|
|
dg.giveme[1].AttachToOwner(dg);
|
|
dg.SetPickupState();
|
|
}
|
|
else
|
|
{
|
|
// swap in-place
|
|
let d = user.DropInventory(sw);
|
|
d.SetOrigin(pos,false);
|
|
d.angle = angle;
|
|
d.vel = vel;
|
|
d.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
// don't autoswitch just yet (hacky)
|
|
if ( swapto )
|
|
{
|
|
user.player.ReadyWeapon = null;
|
|
user.player.PendingWeapon = WP_NOCHANGE;
|
|
}
|
|
}
|
|
bUsePickup = true;
|
|
Touch(user);
|
|
bUsePickup = false;
|
|
// we got picked up
|
|
if ( bDestroyed || Owner || !bSPECIAL )
|
|
{
|
|
// autoswitch to us if we got swapped
|
|
if ( swapto ) user.A_SelectWeapon(giveme[0].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,ignoreallactors:true);
|
|
// 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 bool TryPickup( in out Actor toucher )
|
|
{
|
|
if ( giveme[0].HasSwapWeapon(toucher) )
|
|
return false;
|
|
let cur = toucher.FindInventory(giveme[0].GetClass());
|
|
if ( !cur )
|
|
{
|
|
// give both guns and go away
|
|
if ( !giveme[1].CallTryPickup(toucher) ) giveme[1].Destroy();
|
|
if ( !giveme[0].CallTryPickup(toucher) ) giveme[0].Destroy();
|
|
Spawn(PickupFlash,pos,ALLOW_REPLACE);
|
|
PrintPickupMessage(toucher.CheckLocalView(),GetTag());
|
|
PlayPickupSound(toucher);
|
|
if ( toucher.player ) toucher.player.bonuscount = BONUSADD;
|
|
GoAwayAndDie();
|
|
return true;
|
|
}
|
|
else if ( cur.Amount < 2 )
|
|
{
|
|
// give one gun
|
|
if ( !giveme[1].CallTryPickup(toucher) ) giveme[1].Destroy();
|
|
// drop the other where we stand
|
|
giveme[0].BecomePickup();
|
|
giveme[0].bDROPPED = bDROPPED;
|
|
giveme[0].SetOrigin(pos,false);
|
|
giveme[0].Angle = Angle;
|
|
giveme[0].Vel = Vel;
|
|
giveme[0].bNoGravity = false;
|
|
giveme[0].ClearCounters();
|
|
giveme[0].FloatBobPhase = FloatBobPhase;
|
|
// don't forget to reattach its glow
|
|
if ( giveme[0].PickupFlash is 'SWWMPickupFlash' )
|
|
{
|
|
let p = Spawn(giveme[0].PickupFlash,giveme[0].pos);
|
|
p.target = giveme[0];
|
|
p.SetStateLabel("Pickup");
|
|
}
|
|
// and then go away
|
|
Spawn(PickupFlash,pos,ALLOW_REPLACE);
|
|
PrintPickupMessage(toucher.CheckLocalView(),giveme[0].PickupMessage());
|
|
PlayPickupSound(toucher);
|
|
if ( toucher.player ) toucher.player.bonuscount = BONUSADD;
|
|
GoAwayAndDie();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SetPickupState()
|
|
{
|
|
/*if ( giveme[0] is 'PlasmaBlast' )
|
|
{
|
|
SetTag("$T_PLASMABLAST2");
|
|
SetState(SpawnState+1);
|
|
}
|
|
else*/
|
|
{
|
|
SetTag("$T_EXPLODIUM2");
|
|
SetState(SpawnState);
|
|
}
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 AB -1; // A: Explodium Gun, B: Plasma Blaster
|
|
Stop;
|
|
}
|
|
}
|