swwmgz_m/zscript/items/swwm_ammoextra.zsc
Marisa the Magician e5e6ce619c Fix severe performance issues in large maps.
So... Remember that one decision I made about avoiding BlockThingsIterator as
much as possible? Turns out that was a stupid idea. There ARE situations where
it's better to iterate sector thinglists, yes, especially for things that are
NOT part of the blockmap, but in other cases, the excess allocations of new
iterators are a reasonable price to pay for the lower perf impact in extreme
cases, such as maps that have a gazillion sectors with gazillions of things in
them (I'm looking at you, UDMF mappers).
As a compromise, at least, in situations where the thinglists are needed, I did
add a sort of micro-optimization by implementing code to check if a bounding
box is inside a sector (would be nice if this was part of GZDoom itself, tho).
2023-12-19 11:46:29 +01:00

607 lines
17 KiB
Text

// ============================================================================
// Ammo fabricator
// ============================================================================
Class AmmoFabricator : Inventory abstract
{
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
Mixin SWWMRotatingPickup;
Mixin SWWMPickupGlow;
meta int budget, pertype, maxunits, maxtypes, maxunitprice, txtcol;
meta int chancediv;
String pickupmsgextra;
Property Budget : budget;
Property PerType : pertype;
Property MaxUnits : maxunits;
Property MaxTypes : maxtypes;
Property MaxUnitPrice : maxunitprice;
Property ChanceFactor : chancediv;
Property TextColor : txtcol;
override Inventory CreateCopy( Actor other )
{
// additional lore
SWWMLoreLibrary.Add(other.player,"Fabricator");
return Super.CreateCopy(other);
}
private bool CmpFabAmmo( Class<SWWMAmmo> a, Class<SWWMAmmo> b )
{
let ia = Owner.FindInventory(a);
int cnta = ia?ia.Amount:0;
int maxa = ia?ia.MaxAmount:GetDefaultByType(a).Amount;
let ib = Owner.FindInventory(b);
int cntb = ib?ib.Amount:0;
int maxb = ib?ib.MaxAmount:GetDefaultByType(b).Amount;
double facta = cnta/double(maxa);
double factb = cntb/double(maxb);
return (facta >= factb);
}
private int partition_fabammo( Array<Class<SWWMAmmo> > a, int l, int h )
{
Class<SWWMAmmo> pv = a[h];
int i = (l-1);
for ( int j=l; j<=(h-1); j++ )
{
if ( CmpFabAmmo(pv,a[j]) )
{
i++;
Class<SWWMAmmo> tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
Class<SWWMAmmo> tmp = a[h];
a[h] = a[i+1];
a[i+1] = tmp;
return i+1;
}
private void qsort_fabammo( Array<Class<SWWMAmmo> > a, int l, int h )
{
if ( l >= h ) return;
int p = partition_fabammo(a,l,h);
qsort_fabammo(a,l,p-1);
qsort_fabammo(a,p+1,h);
}
private bool CmpFabAmmo_chance( Class<SWWMAmmo> a, Class<SWWMAmmo> b )
{
int cha = GetDefaultByType(a).Accuracy;
int chb = GetDefaultByType(b).Accuracy;
return (cha >= chb);
}
private int partition_fabammo_chance( Array<Class<SWWMAmmo> > a, int l, int h )
{
Class<SWWMAmmo> pv = a[h];
int i = (l-1);
for ( int j=l; j<=(h-1); j++ )
{
if ( CmpFabAmmo_chance(pv,a[j]) )
{
i++;
Class<SWWMAmmo> tmp = a[j];
a[j] = a[i];
a[i] = tmp;
}
}
Class<SWWMAmmo> tmp = a[h];
a[h] = a[i+1];
a[i+1] = tmp;
return i+1;
}
private void qsort_fabammo_chance( Array<Class<SWWMAmmo> > a, int l, int h )
{
if ( l >= h ) return;
int p = partition_fabammo_chance(a,l,h);
qsort_fabammo_chance(a,l,p-1);
qsort_fabammo_chance(a,p+1,h);
}
override String PickupMessage()
{
if ( pickupmsgextra != "" ) return String.Format("\c%c%s\c-\n%s",0x61+txtcol,StringTable.Localize(pickupmsg),pickupmsgextra);
return pickupmsg;
}
bool FabricateAmmo()
{
Array<Class<SWWMAmmo> > available;
// populate ammo production list
foreach ( cls:AllActorClasses )
{
let a = (Class<SWWMAmmo>)(cls);
// skip over candy gun spares, they're "special ammo"
if ( a == 'CandyGunSpares' ) continue;
// only direct descendants of swwmammo with a set price below our max unit price
if ( !a || (a.GetParentClass() != 'SWWMAmmo') ) continue;
let def = GetDefaultByType(a);
if ( (def.Stamina <= 0) || (def.Stamina > maxunitprice) ) continue;
let f = Owner.FindInventory(a);
// don't include maxed out ammo
if ( f && (f.Amount >= f.MaxAmount) ) continue;
available.Push(a);
}
// sort by drop chance
qsort_fabammo_chance(available,0,available.Size()-1);
// discard some candidates based on their random drop chance (leaving AT LEAST one)
for ( int i=0; i<available.Size(); i++ )
{
if ( available.Size() == 1 ) break;
int chance = GetDefaultByType(available[i]).Accuracy;
if ( chancediv <= 0 ) chance = 0;
else chance /= chancediv;
if ( Random[DropChance](1,100) > chance ) continue;
available.Delete(i);
i--;
}
// sort by "need weight" (prioritize ammo that the player lacks over ammo that the player has plenty of)
qsort_fabammo(available,0,available.Size()-1);
// crop by "max types"
if ( available.Size() > maxtypes ) available.Resize(maxtypes);
// loop through until we fill the inventory or run out of budget
bool given = false;
int consumed = 0;
String fabstr = "";
bool comma = false;
int tpertype = pertype;
int ttotal = maxunits;
foreach ( type:available )
{
int amt, lim;
int cnt = 0;
SWWMAmmo cur = SWWMAmmo(Owner.FindInventory(type));
if ( cur )
{
amt = cur.Amount;
lim = cur.MaxAmount;
}
else
{
cur = SWWMAmmo(Spawn(type));
amt = cur.Amount = 0;
lim = cur.MaxAmount;
cur.AttachToOwner(Owner);
}
// percentage based on DEFAULT max amount (capped at 1 minimum)
if ( pertype < 0 ) tpertype = max(1,-int(cur.default.MaxAmount*pertype*.01));
tpertype = min(tpertype,ttotal);
while ( (amt < lim) && (consumed+cur.default.Stamina < budget) && (cnt < tpertype) )
{
consumed += cur.default.Stamina;
amt = ++cur.Amount;
cnt++;
given = true;
}
if ( cnt > 0 )
{
String tstr = String.Format("%d %s",cnt,(cnt>1)?StringTable.Localize("$T_"..cur.PickupTag.."S"):StringTable.Localize("$T_"..cur.PickupTag));
if ( comma ) fabstr = fabstr..", "..tstr;
else fabstr = tstr;
comma = true;
}
ttotal -= cnt;
if ( ttotal <= 0 ) break;
}
if ( given ) pickupmsgextra = fabstr;
else pickupmsgextra = "";
return given;
}
override bool Use( bool pickup )
{
if ( FabricateAmmo() )
{
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) && !bQUIET ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
return true;
}
return false;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetZ(floorz); // gee whizz thanks Hexen
}
Default
{
+INVENTORY.AUTOACTIVATE;
+FLOATBOB;
+DONTGIB;
Inventory.UseSound "fabricator/use";
Inventory.PickupFlash "SWWMPickupFlash";
Inventory.MaxAmount 0;
FloatBobStrength 0.25;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class FabricatorTier1 : AmmoFabricator
{
Mixin SWWMAutoUseFix;
Default
{
Tag "$T_FABRICATOR1";
Inventory.PickupMessage "$T_FABRICATOR1";
AmmoFabricator.Budget 3000;
AmmoFabricator.PerType 1;
AmmoFabricator.MaxUnits 2;
AmmoFabricator.MaxTypes 2;
AmmoFabricator.MaxUnitPrice 2500;
AmmoFabricator.ChanceFactor 1;
AmmoFabricator.TextColor Font.CR_BLUE;
// Completely stupid hitbox, but necessary to not break compat
Radius 8;
Height 8;
}
}
Class FabricatorTier2 : AmmoFabricator
{
Mixin SWWMAutoUseFix;
Default
{
Tag "$T_FABRICATOR2";
Inventory.PickupMessage "$T_FABRICATOR2";
AmmoFabricator.Budget 20000;
AmmoFabricator.PerType 2;
AmmoFabricator.MaxUnits 5;
AmmoFabricator.MaxTypes 3;
AmmoFabricator.MaxUnitPrice 18000;
AmmoFabricator.ChanceFactor 2;
AmmoFabricator.TextColor Font.CR_GREEN;
// Completely stupid hitbox, but necessary to not break compat
Radius 8;
Height 8;
}
}
Class FabricatorTier3 : AmmoFabricator
{
Mixin SWWMAutoUseFix;
Default
{
Tag "$T_FABRICATOR3";
Inventory.PickupMessage "$T_FABRICATOR3";
AmmoFabricator.Budget 60000;
AmmoFabricator.PerType -25;
AmmoFabricator.MaxUnits 10;
AmmoFabricator.MaxTypes 4;
AmmoFabricator.MaxUnitPrice 50000;
AmmoFabricator.ChanceFactor 4;
AmmoFabricator.TextColor Font.CR_RED;
// Completely stupid hitbox, but necessary to not break compat
Radius 8;
Height 8;
}
}
Class FabricatorTier4 : AmmoFabricator
{
Mixin SWWMAutoUseFix;
Default
{
Tag "$T_FABRICATOR4";
Inventory.PickupMessage "$T_FABRICATOR4";
AmmoFabricator.Budget int.max;
AmmoFabricator.PerType -50;
AmmoFabricator.MaxUnits int.max;
AmmoFabricator.MaxTypes int.max;
AmmoFabricator.MaxUnitPrice 1000000;
AmmoFabricator.ChanceFactor 0;
AmmoFabricator.TextColor Font.CR_GOLD;
}
}
// ============================================================================
// Hammerspace embiggener
// ============================================================================
Class HammerspaceEmbiggener : Inventory
{
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
Mixin SWWMRotatingPickup;
override Inventory CreateCopy( Actor other )
{
bool traded = (GetClass()=='TradedHammerspaceEmbiggener');
if ( !traded ) other.A_StartSound("powerup/embiggener",CHAN_ITEMEXTRA);
// Find every unique type of ammoitem. Give it to the player if
// they don't have it already, and increase its maximum capacity.
foreach ( cls:AllActorClasses )
{
let type = (class<Ammo>)(cls);
if ( !type || (type.GetParentClass() != 'SWWMAmmo') ) continue;
let ammoitem = Ammo(other.FindInventory(type));
int amount = GetDefaultByType(type).BackpackAmount*self.Amount;
if ( traded ) amount = 0;
if ( amount < 0 ) amount = 0;
if ( !ammoitem )
{
// The player did not have the ammoitem. Add it.
ammoitem = Ammo(Spawn(type));
ammoitem.Amount = amount;
if ( ammoitem.BackpackMaxAmount > 0 )
{
double factor = (ammoitem.BackpackMaxAmount-ammoitem.default.MaxAmount)/double(MaxAmount);
ammoitem.MaxAmount = int(ammoitem.default.MaxAmount+min(self.Amount,MaxAmount)*factor);
}
if ( (ammoitem.Amount > ammoitem.MaxAmount) && !sv_unlimited_pickup )
ammoitem.Amount = ammoitem.MaxAmount;
ammoitem.AttachToOwner(other);
}
else
{
// The player had the ammoitem. Give some more.
if ( ammoitem.BackpackMaxAmount > 0 )
{
double factor = (ammoitem.BackpackMaxAmount-ammoitem.default.MaxAmount)/double(MaxAmount);
ammoitem.MaxAmount = int(ammoitem.default.MaxAmount+min(self.Amount,MaxAmount)*factor);
}
if ( ammoitem.Amount < ammoitem.MaxAmount )
{
if ( (ammoitem.Amount > 0) && (ammoitem.Amount+amount < 0) )
ammoitem.Amount = int.max;
else ammoitem.Amount += amount;
if ( (ammoitem.Amount > ammoitem.MaxAmount) && !sv_unlimited_pickup )
ammoitem.Amount = ammoitem.MaxAmount;
}
}
}
// do the same for mag ammo, in a separate loop
foreach ( cls:AllActorClasses )
{
let type = (class<MagAmmo>)(cls);
if ( !type || (type.GetParentClass() != 'MagAmmo') ) continue;
let magitem = MagAmmo(other.FindInventory(type));
int amount = GetDefaultByType(type).BackpackAmount*self.Amount;
if ( traded ) amount = 0;
if ( amount < 0 ) amount = 0;
int mags = amount/GetDefaultByType(type).ClipSize;
amount = amount%GetDefaultByType(type).ClipSize;
if ( !magitem )
{
// The player did not have the magitem. Add it.
magitem = MagAmmo(Spawn(type));
magitem.Amount = amount;
magitem.AttachToOwner(other);
// by this point, we assume that the parent ammo pointer is valid, so...
let ammoitem = magitem.pamo;
// append some mags to it
if ( ammoitem.Amount < ammoitem.MaxAmount )
{
if ( (ammoitem.Amount > 0) && (ammoitem.Amount+mags < 0) )
ammoitem.Amount = int.max;
else ammoitem.Amount += mags;
if ( (ammoitem.Amount > ammoitem.MaxAmount) && !sv_unlimited_pickup )
ammoitem.Amount = ammoitem.MaxAmount;
}
// we can't add extra mags, just max out the spare ammo
else if ( mags > 0 ) magitem.Amount = min(magitem.MaxAmount,magitem.Amount+magitem.ClipSize);
}
else
{
if ( magitem.Amount+amount >= magitem.MaxAmount )
{
mags++;
amount -= magitem.MaxAmount;
}
magitem.Amount += amount;
let ammoitem = magitem.pamo;
// append some mags to it
if ( ammoitem.Amount < ammoitem.MaxAmount )
{
if ( (ammoitem.Amount > 0) && (ammoitem.Amount+mags < 0) )
ammoitem.Amount = int.max;
else ammoitem.Amount += mags;
if ( (ammoitem.Amount > ammoitem.MaxAmount) && !sv_unlimited_pickup )
ammoitem.Amount = ammoitem.MaxAmount;
}
// we can't add extra mags, just max out the spare ammo
else if ( mags > 0 ) magitem.Amount = min(magitem.MaxAmount,magitem.Amount+magitem.ClipSize);
}
}
self.Amount = min(self.Amount,MaxAmount);
if ( GetParentClass() == 'HammerspaceEmbiggener' )
{
if ( !GoAway() ) Destroy();
let copy = Inventory(Spawn('HammerspaceEmbiggener'));
copy.ClearCounters();
copy.Amount = self.Amount;
copy.MaxAmount = self.MaxAmount;
return copy;
}
if ( GoAway() )
{
let copy = Inventory(Spawn(GetClass()));
copy.ClearCounters();
copy.Amount = self.Amount;
copy.MaxAmount = self.MaxAmount;
return copy;
}
return self;
}
override bool HandlePickup( Inventory item )
{
if ( (item.GetClass() == GetClass()) || (item.GetParentClass() == 'HammerspaceEmbiggener') )
{
bool traded = (item.GetClass()=='TradedHammerspaceEmbiggener');
if ( !traded ) Owner.A_StartSound("powerup/embiggener",CHAN_ITEMEXTRA);
if ( (Amount > 0) && (Amount+item.Amount < 0) )
Amount = int.max;
else Amount += item.Amount;
if ( Amount > MaxAmount ) Amount = MaxAmount;
item.bPickupGood = true;
// readjust ammo values to new capacity
for ( Inventory i=Owner.Inv; i; i=i.Inv )
{
if ( !(i is 'Ammo') ) continue;
if ( Ammo(i).BackpackMaxAmount > 0 )
{
double factor = (Ammo(i).BackpackMaxAmount-i.default.MaxAmount)/double(MaxAmount);
i.MaxAmount = int(i.default.MaxAmount+Amount*factor);
}
int amount = Ammo(i).BackpackAmount*item.Amount;
if ( traded ) amount = 0;
if ( (i.Amount > 0) && (i.Amount+amount < 0) )
i.Amount = int.max;
else i.Amount += amount;
if ( (i.Amount > i.MaxAmount) && !sv_unlimited_pickup )
i.Amount = i.MaxAmount;
}
if ( traded ) return true;
// give spare mag ammo separately
for ( Inventory i=Owner.Inv; i; i=i.Inv )
{
if ( !(i is 'MagAmmo') ) continue;
int amount = MagAmmo(i).BackpackAmount*item.Amount;
int mags = amount/MagAmmo(i).ClipSize;
amount = amount%MagAmmo(i).ClipSize;
if ( i.Amount+amount >= MagAmmo(i).ClipSize )
{
mags++;
amount -= MagAmmo(i).ClipSize;
}
i.Amount += amount;
Ammo a = MagAmmo(i).pamo;
if ( a.Amount < a.MaxAmount )
{
if ( (a.Amount > 0) && (a.Amount+mags < 0) )
a.Amount = int.max;
else a.Amount += mags;
if ( (a.Amount > a.MaxAmount) && !sv_unlimited_pickup )
a.Amount = a.MaxAmount;
}
else if ( mags > 0 ) i.Amount = min(i.MaxAmount,i.Amount+MagAmmo(i).ClipSize);
}
return true;
}
// new ammo suddenly added? upgrade it (this shouldn't happen unless weird scripting has been involved)
if ( (item is 'Ammo') && !Owner.FindInventory(Ammo(item).GetParentAmmo()) )
{
if ( Ammo(item).BackpackMaxAmount > 0 )
{
double factor = (Ammo(item).BackpackMaxAmount-item.default.MaxAmount)/double(MaxAmount);
item.MaxAmount = int(item.default.MaxAmount+Amount*factor);
}
}
return false;
}
override void DepleteOrDestroy()
{
// reset upgrade
for ( Inventory i=Owner.Inv; i; i=i.Inv )
{
if ( !(i is 'Ammo') ) continue;
i.MaxAmount = i.default.MaxAmount;
if ( i.Amount > i.MaxAmount ) i.Amount = i.MaxAmount;
}
Super.DepleteOrDestroy();
}
// merges overlapping embiggeners into a bulk upgrade
void A_MergeEmbiggeners()
{
// while we're at it, adjust our height,
// since backpacks are taller in Doom
if ( gameinfo.gametype&GAME_DoomChex )
A_SetSize(-1,26);
int tamount = Amount;
for ( Actor t=CurSector.thinglist; t; )
{
let next = t.snext;
if ( (t == self) || !(t is 'HammerspaceEmbiggener') || !(t.spawnpoint ~== spawnpoint) )
{
t = next;
continue;
}
tamount += HammerspaceEmbiggener(t).Amount;
t.ClearCounters();
t.Destroy();
t = next;
}
if ( tamount <= 1 ) return;
tamount -= tamount%2; // always even numbered
if ( GetClass() == 'BulkHammerspaceEmbiggener' )
{
Amount = min(tamount,MaxAmount);
return;
}
let n = Spawn("BulkHammerspaceEmbiggener",pos);
Inventory(n).Amount = min(tamount,MaxAmount);
SWWMUtility.TransferItemProp(self,n);
ClearCounters();
Destroy();
}
Default
{
Tag "$T_EMBIGGENER";
Stamina -800000;
Inventory.PickupMessage "$T_EMBIGGENER";
Inventory.MaxAmount 8;
Inventory.InterHubAmount 8;
Inventory.PickupFlash "SWWMPickupFlash";
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
+INVENTORY.ALWAYSPICKUP;
+DONTGIB;
+FLOATBOB;
FloatBobStrength 0.25;
}
States
{
Spawn:
XZW1 A 0;
XZW1 A -1 A_MergeEmbiggeners();
Stop;
}
}
// used when cheating or trading, this version does not give ammo and is meant
// only for GiveInventory, so it shouldn't be spawned in the world
Class TradedHammerspaceEmbiggener : HammerspaceEmbiggener {}
// used to denote "merged" embiggeners, changes color based on amount
// green (2+)
// blue (4+)
// purple (6+)
// black (8+)
Class BulkHammerspaceEmbiggener : HammerspaceEmbiggener
{
override string PickupMessage()
{
return String.Format("%dx %s",Amount,StringTable.Localize("$T_BULKEMBIGGENER"));
}
States
{
Spawn:
XZW1 A 0;
XZW1 A -1
{
A_MergeEmbiggeners();
if ( bDestroyed ) return ResolveState(null);
if ( Amount > 1 ) return SpawnState+1+min(4,Amount/2);
return ResolveState(null);
}
XZW1 BCDE -1;
Stop;
}
}