swwmgz_m/zscript/weapons/swwm_baseweapon.zsc
Marisa the Magician aaa65af795 Add A_CanBob() function to weapons to force-enable bobbing within a state.
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).
2024-01-24 13:32:24 +01:00

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;
}
}