// ============================================================================ // 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 a, Class 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 > a, int l, int h ) { Class pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpFabAmmo(pv,a[j]) ) { i++; Class tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } Class tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_fabammo( Array > 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 a, Class b ) { int cha = GetDefaultByType(a).Accuracy; int chb = GetDefaultByType(b).Accuracy; return (cha >= chb); } private int partition_fabammo_chance( Array > a, int l, int h ) { Class pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpFabAmmo_chance(pv,a[j]) ) { i++; Class tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } Class tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_fabammo_chance( Array > 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 > available; // populate ammo production list foreach ( cls:AllActorClasses ) { let a = (Class)(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 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 6000; AmmoFabricator.PerType -15; AmmoFabricator.MaxUnits 5; 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 -20; AmmoFabricator.MaxUnits 10; 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 15; 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)(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)(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; } }