918 lines
27 KiB
Text
918 lines
27 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;
|
|
|
|
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);
|
|
// 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) )
|
|
{
|
|
if ( !(self is 'Demolitionist') ) return;
|
|
Demolitionist(self).BumpView(factor,dir);
|
|
}
|
|
action void A_BumpAngle( double factor )
|
|
{
|
|
if ( !(self is 'Demolitionist') ) return;
|
|
Demolitionist(self).bumpangle += factor;
|
|
}
|
|
|
|
// 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
|
|
int sellprice = abs(item.Stamina)/2;
|
|
SWWMScoreObj.Spawn(sellprice,level.Vec3Offset(Owner.pos,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.+(0,0,Owner.Height/2)));
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player )
|
|
{
|
|
if ( Owner.player == players[consoleplayer] ) 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();
|
|
}
|
|
override void PlayUpSound( Actor origin )
|
|
{
|
|
if ( UpSound ) origin.A_StartSound(UpSound,CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
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);
|
|
// 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;
|
|
}
|
|
}
|