swwmgz_m/zscript/utility/swwm_utility_item.zsc

327 lines
10 KiB
Text

// inventory-related functions
extend Class SWWMUtility
{
// full reset of inventory (excluding collectibles, and optionally resetting the score)
static play void WipeInventory( Actor mo, bool resetscore = false, bool allplayers = false )
{
if ( allplayers )
{
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
WipeInventory(players[i].mo,resetscore,false);
}
return;
}
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
SWWMCredits c = SWWMCredits.Find(p);
if ( resetscore && c ) c.credits = 0;
Actor last = p.mo;
while ( last.inv )
{
let inv = last.inv;
if ( !(inv is 'SWWMCollectible') )
{
inv.Destroy();
if ( !inv.bDestroyed ) last = inv;
}
else last = inv;
}
p.mo.GiveDefaultInventory();
p.mo.BringUpWeapon();
p.health = p.mo.Health = p.mo.SpawnHealth();
}
// sets all carried ammo back to zero
// resets hammerspace capacity
static play void ResetAmmo( Actor mo )
{
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
for ( Inventory i=p.mo.inv; i; i=i.inv )
{
if ( (i is 'Ammo') || (i is 'MagAmmo') )
{
i.Amount = 0;
i.MaxAmount = i.default.MaxAmount;
}
if ( i is 'HammerspaceEmbiggener' )
i.Amount = 0;
}
// also gives back any ammo from carried weapons (provided they don't have NOFIRSTGIVE)
for ( Inventory i=p.mo.inv; i; i=i.inv )
{
if ( !(i is 'Weapon') ) continue;
let w = Weapon(i);
if ( (w is 'SWWMWeapon') && SWWMWeapon(w).bNOFIRSTGIVE ) continue;
if ( w.Ammo1 ) w.Ammo1.Amount = min(w.Ammo1.MaxAmount,w.Ammo1.Amount+w.default.AmmoGive1);
if ( w.Ammo2 && (w.Ammo2 != w.Ammo1) ) w.Ammo2.Amount = min(w.Ammo2.MaxAmount,w.Ammo2.Amount+w.default.AmmoGive2);
}
}
// removes all usable items
static play void ResetItems( Actor mo )
{
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
Actor last = p.mo;
while ( last.inv )
{
let inv = last.inv;
if ( !inv.bINVBAR )
{
last = inv;
continue;
}
inv.Destroy();
if ( !inv.bDestroyed ) last = inv;
}
}
// resets health and removes worn armor
static play void ResetHealth( Actor mo )
{
PlayerInfo p = mo.player;
if ( !p || !p.mo ) return;
p.health = p.mo.health = 100;
for ( Inventory i=p.mo.inv; i; i=i.inv )
{
if ( !(i is 'SWWMArmor') ) continue;
i.Amount = 0;
}
}
// what RandomSpawner does, basically (simplified for items)
static play void TransferItemProp( Actor a, Actor b, bool bundlehack = false )
{
if ( bundlehack )
{
b.spawnpoint = b.pos;
b.spawnangle = int(b.angle);
}
else
{
b.spawnpoint = a.spawnpoint;
b.spawnangle = a.spawnangle;
b.angle = a.angle;
b.pitch = a.pitch;
b.roll = a.roll;
}
b.special = a.special;
b.FloatBobPhase = a.FloatBobPhase; // important
for ( int i=0; i<5; i++ ) b.args[i] = a.args[i];
b.special1 = a.special1;
b.special2 = a.special2;
b.spawnflags = a.spawnflags&~MTF_SECRET;
b.HandleSpawnFlags();
b.spawnflags = a.spawnflags;
b.bCountSecret = a.spawnflags&MTF_SECRET;
b.ChangeTid(a.tid);
b.vel = b.vel;
b.master = b.master;
b.tracer = b.tracer;
b.target = b.target;
b.bDROPPED = a.bDROPPED;
}
// check that all players can get enough of this if needed
// multi: check for multiple copies, not just single instances
// (useful e.g. for dual wieldable weapons)
static bool CheckNeedsItem( Class<Inventory> itm, bool multi = false )
{
int np = 0;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
np++;
}
int required = np;
if ( multi ) required *= GetDefaultByType(itm).MaxAmount;
// subtract all that exist already (either in world or owned)
let ti = ThinkerIterator.Create(itm);
Inventory i;
while ( i = Inventory(ti.Next()) )
{
if ( multi ) required -= i.Amount;
else required--;
}
// check travelling inventory separately, as by default iterators don't check anything below STAT_FIRST_THINKING
ti = ThinkerIterator.Create(itm,Thinker.STAT_TRAVELLING);
while ( i = Inventory(ti.Next()) )
{
if ( multi ) required -= i.Amount;
else required--;
}
return (required>0);
}
// checks if instances of a certain item exist
// skipme: optionally, ignore checking for one specific instance
// (useful to check if we're the only copy of an item)
// mapstart: this function is being called during map load, so we
// should also check STAT_TRAVELLING inventory
// worldonly: only checks for items that are placed in the world
// ownedonly: only checks for items that are owned by players
// (note that this is mutually exclusive with worldonly)
static bool ItemExists( Class<Inventory> itm, Inventory skipme = null, bool mapstart = false, bool worldonly = false, bool ownedonly = false )
{
let ti = ThinkerIterator.Create(itm);
Inventory i;
while ( i = Inventory(ti.Next()) )
{
if ( i == skipme ) continue;
if ( worldonly && i.Owner ) continue;
if ( ownedonly && (!i.Owner || !i.Owner.player) ) continue;
return true;
}
if ( worldonly || !mapstart ) return false;
ti = ThinkerIterator.Create(itm,Thinker.STAT_TRAVELLING);
while ( i = Inventory(ti.Next()) )
{
if ( i == skipme ) continue;
if ( ownedonly && (!i.Owner || !i.Owner.player) ) continue;
return true;
}
return false;
}
// multi-weapon spawn stuff
static private Class<Weapon> PickPair( Class<Weapon> a, Class<Weapon> b )
{
if ( !ItemExists(a,mapstart:true) ) return a;
if ( !ItemExists(b,mapstart:true) ) return b;
return Random[Replacements](0,1)?a:b;
}
// melee weapon + extra slot 2 guns
static Class<Weapon> PickSWWMSlot1()
{
// [GROSS HACK] default to a hammer if there are no players
// (this genuinely can happen, if player starts were placed AFTER the item)
int np = 0;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
np++;
}
if ( np <= 0 ) return 'ItamexHammer';
// so the player can recover it if they decided to drop it in a previous map, or they didn't start with it
if ( CheckNeedsItem('DeepImpact') ) return 'DeepImpact';
if ( CheckNeedsItem('ItamexHammer') ) return 'ItamexHammer';
return PickSWWMSlot2();
}
// pistol spawn, pretty simple
static Class<Weapon> PickSWWMSlot2()
{
// as they are dual-wieldable, there should be a 50% chance for spares to also appear if needed
if ( Random[Replacements](0,1) && !CheckNeedsItem('ExplodiumGun') && CheckNeedsItem('ExplodiumGun',true) )
return 'ExplodiumGun';
if ( Random[Replacements](0,1) && !CheckNeedsItem('PlasmaBlast') && CheckNeedsItem('PlasmaBlast',true) )
return 'PlasmaBlast';
return PickPair('ExplodiumGun','PlasmaBlast');
}
// shotgun spawn
static Class<Weapon> PickSWWMSlot3()
{
return PickPair('Spreadgun','PuntzerBeta');
}
// super shotgun spawn
static Class<Weapon> PickSWWMSlot4()
{
return PickPair('Wallbuster','PuntzerGamma');
}
// chaingun spawn
static Class<Weapon> PickSWWMSlot5()
{
return PickPair('Eviscerator','HeavyMahSheenGun');
}
// rocket launcher spawn
static Class<Weapon> PickSWWMSlot6()
{
return PickPair('Hellblazer','Quadravol');
}
// first plasma rifle spawn
static Class<Weapon> PickSWWMSlot7()
{
return PickPair('Sparkster','ModernSparkster');
}
// second plasma rifle spawn
static Class<Weapon> PickSWWMSlot8()
{
return PickPair('SilverBullet','RayKhom');
}
// first bfg spawn
static Class<Weapon> PickSWWMSlot9()
{
// 25% chance to still drop another candy gun if it's not at max capacity
if ( !Random[Replacements](0,3) && ItemExists('CandyGun') && CheckNeedsItem('CandyGunSpares',true) )
return 'CandyGun';
return PickPair('CandyGun','MisterRifle');
}
// second bfg spawn (each weapon can only exist once)
static Class<Weapon> PickSWWMSlot0( bool fallback = true )
{
if ( ItemExists('Ynykron',mapstart:true) )
{
if ( ItemExists('RafanKos',mapstart:true) )
return fallback?PickSWWMSlot9():null;
return 'RafanKos';
}
if ( ItemExists('RafanKos',mapstart:true) )
return 'Ynykron';
return Random[Replacements](0,1)?'Ynykron':'RafanKos';
}
// either plasma rifle spawn
static Class<Weapon> PickDoomSlot6()
{
bool hasslot7 = (!CheckNeedsItem('Sparkster')||!CheckNeedsItem('ModernSparkster'));
bool hasslot8 = (!CheckNeedsItem('SilverBullet')||!CheckNeedsItem('RayKhom'));
// if the player already has a slot 7 weapon...
if ( hasslot7 )
{
// ... and also has a slot 8 weapon, 33% chance of a slot 8 spawn
// otherwise, guaranteed slot 8 spawn
if ( hasslot8 && Random[Replacements](0,2) ) return PickSWWMSlot7();
else return PickSWWMSlot8();
}
// otherwise, always spawn a slot 7 weapon first
return PickSWWMSlot7();
}
// either bfg spawn
static Class<Weapon> PickDoomSlot7()
{
bool hasslot9 = (!CheckNeedsItem('CandyGun')||!CheckNeedsItem('MisterRifle'));
bool hasslot0 = (!CheckNeedsItem('Ynykron')||!CheckNeedsItem('RafanKos'));
let rep = PickSWWMSlot0(false);
// if the player already has a slot 9 weapon (and a slot 0 weapon can still spawn)...
if ( hasslot9 && rep )
{
// ... and also has a slot 0 weapon already, 33% chance of a slot 0 spawn
// otherwise, guaranteed slot 0 spawn
if ( hasslot0 && Random[Replacements](0,2) ) return PickSWWMSlot9();
else return rep;
}
// otherwise, always spawn a slot 9 weapon first
return PickSWWMSlot9();
}
// either shotgun spawn (also used for Heretic)
static Class<Weapon> PickDoomSlot3()
{
// always slot 3 after map start, prevents shotgun guys from dropping wallbusters, which is weird af
if ( level.maptime ) return PickSWWMSlot3();
bool hasslot3 = (!CheckNeedsItem('Spreadgun')||!CheckNeedsItem('PuntzerBeta'));
bool hasslot4 = (!CheckNeedsItem('Wallbuster')||!CheckNeedsItem('PuntzerGamma'));
// if the player already has a slot 3 weapon...
if ( hasslot3 )
{
// ... and also has a slot 4 weapon, 33% chance of a slot 4 spawn
// otherwise, guaranteed slot 4 spawn
if ( hasslot4 && Random[Replacements](0,2) ) return PickSWWMSlot3();
return PickSWWMSlot4();
}
// otherwise, always spawn a slot 3 weapon first
return PickSWWMSlot3();
}
}