// Common code for ammo division Class SWWMAmmo : Ammo { Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMRotatingPickup; Mixin SWWMUnrealStyleDrop; meta String PickupTag; meta Class MagAmmoType; private int SAmmoFlags; Property PickupTag : PickupTag; Property MagAmmoType : MagAmmoType; FlagDef UsePickupMsg : SAmmoFlags, 0; // use the set pickup message rather than generating from pickup tag override Class GetParentAmmo() { Class type = GetClass(); while ( (type.GetParentClass() != "SWWMAmmo") && type.GetParentClass() ) type = type.GetParentClass(); return (Class)(type); } override string PickupMessage() { if ( bUsePickupMsg ) return Super.PickupMessage(); String tagstr = "$T_"..PickupTag; if ( Amount > 1 ) { tagstr = tagstr.."S"; return String.Format("%d %s",Amount,StringTable.Localize(tagstr)); } return StringTable.Localize(tagstr); } private Inventory DoDrop( Class type ) { let copy = Inventory(Spawn(type,Owner.Pos,NO_REPLACE)); if ( !copy ) return null; copy.DropTime = 30; copy.bSpecial = copy.bSolid = false; copy.SetOrigin(Owner.Vec3Offset(0,0,10.),false); copy.Angle = Owner.Angle; copy.VelFromAngle(5.); copy.Vel.Z = 1.; copy.Vel += Owner.Vel; copy.bNoGravity = false; copy.ClearCounters(); copy.OnDrop(Owner); copy.vel += (RotateVector((FRandom[Junk](-1.5,.5),FRandom[Junk](-2.5,2.5)),Owner.angle),FRandom[Junk](2.,5.)); return copy; } override bool SpecialDropAction( Actor dropper ) { if ( swwm_enemydrops >= 0 ) { // random chance to not drop if ( Random[DropChance](1,100) <= Accuracy ) return true; if ( Amount == default.Amount ) return false; // subdivide Owner = dropper; // needed for positioning to work CreateTossable(Amount); return true; } // no ammo drops from enemies return true; } private bool CmpAmmo( Class a, Class b ) { let amta = GetDefaultByType(a).Amount; let amtb = GetDefaultByType(b).Amount; return (amta < amtb); } private int partition_ammotypes( Array > a, int l, int h ) { Class pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpAmmo(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_ammotypes( Array > a, int l, int h ) { if ( l >= h ) return; int p = partition_ammotypes(a,l,h); qsort_ammotypes(a,l,p-1); qsort_ammotypes(a,p+1,h); } override inventory CreateTossable( int amt ) { if ( bUndroppable || bUntossable || !Owner || (Amount <= 0) || (amt == 0) ) return null; // cap amt = min(amount,amt); // enumerate all subclasses Array > ammotypes; ammotypes.Clear(); foreach ( cls:AllActorClasses ) { if ( cls is GetParentAmmo() ) ammotypes.Push((Class)(cls)); } // sort from largest to smallest qsort_ammotypes(ammotypes,0,ammotypes.Size()-1); // perform subdivision Inventory last = null; while ( amt > 0 ) { foreach ( type:ammotypes ) { let def = GetDefaultByType(type); if ( amt >= def.Amount ) { last = DoDrop(type); amt -= def.Amount; Amount -= def.Amount; break; } } } return last; } override bool HandlePickup( Inventory item ) { // drop excess ammo if ( !bUNDROPPABLE && !bUNTOSSABLE && (item is 'Ammo') && (Ammo(item).GetParentAmmo() == GetParentAmmo()) ) { int excess = Amount+item.Amount; if ( excess > MaxAmount ) excess -= MaxAmount; if ( excess < item.Amount ) { // enumerate all subclasses Array > ammotypes; ammotypes.Clear(); foreach ( cls:AllActorClasses ) { if ( cls is GetParentAmmo() ) ammotypes.Push((Class)(cls)); } // sort from largest to smallest qsort_ammotypes(ammotypes,0,ammotypes.Size()-1); // drop spares Inventory last; while ( excess > 0 ) { // first of all, see if we can ADD mag ammo if ( MagAmmoType ) { let ma = MagAmmo(Owner.FindInventory(MagAmmoType)); if ( !ma ) { ma = MagAmmo(Spawn(MagAmmoType)); ma.Amount = 0; ma.AttachToOwner(Owner); } if ( ma.Amount <= (ma.MaxAmount-ma.ClipSize) ) { ma.Amount += ma.ClipSize; excess--; continue; } } foreach ( type:ammotypes ) { let def = GetDefaultByType(type); if ( excess >= def.Amount ) { double ang = FRandom[Junk](0,360); last = DoDrop(type); last.SetOrigin(item.pos,false); last.vel.xy = AngleToVector(ang,FRandom[Junk](2,5)); excess -= def.Amount; break; } } } } else if ( MagAmmoType ) { // can we split it into mag ammo? let ma = MagAmmo(Owner.FindInventory(MagAmmoType)); if ( !ma ) { ma = MagAmmo(Spawn(MagAmmoType)); ma.Amount = 0; ma.AttachToOwner(Owner); } if ( !GetDefaultByType(MagAmmoType).bUNDROPPABLE && !GetDefaultByType(MagAmmoType).bUNTOSSABLE ) { if ( ma.Amount < ma.MaxAmount ) { // split into bullets for ( int i=0; i 0 ) ma.CreateTossable(dropamt); ma.Amount = min(ma.MaxAmount,ma.Amount+bul); } item.bPickupGood = true; return true; } } else if ( ma.Amount <= (ma.MaxAmount-ma.ClipSize*item.Amount) ) { // when mag ammo is undroppable, can only divide in full mags EXACTLY ma.Amount += ma.ClipSize*item.Amount; item.bPickupGood = true; return true; } } } return Super.HandlePickup(item); } override void AttachToOwner( Actor other ) { Super.AttachToOwner(other); // attach our mag ammo if we have none if ( MagAmmoType && !Owner.FindInventory(MagAmmoType) ) { let ma = Inventory(Spawn(MagAmmoType)); ma.Amount = 0; ma.AttachToOwner(Owner); } } override void ModifyDropAmount( int dropamount ) { Super.ModifyDropAmount(dropamount); int maxdrop = 1; foreach ( cls:AllActorClasses ) { if ( !(cls is GetParentAmmo()) ) continue; let def = GetDefaultByType((Class)(cls)); maxdrop = max(maxdrop,def.amount); } Amount = Random[ShellDrop](1,clamp(dropamount,1,maxdrop)); } default { +INVENTORY.IGNORESKILL; +DONTGIB; Inventory.PickupFlash "SWWMPickupFlash"; +FLOATBOB; FloatBobStrength 0.25; } } // Common code for individual bullets Class MagAmmo : Inventory abstract { Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMRotatingPickup; Mixin SWWMUnrealStyleDrop; meta Class ParentAmmo; Ammo pamo; meta int ClipSize; meta String PickupTag; int BackpackAmount; Property ParentAmmo : ParentAmmo; Property ClipSize : ClipSize; Property PickupTag : PickupTag; Property BackpackAmount : BackpackAmount; default { +INVENTORY.KEEPDEPLETED; +DONTGIB; Inventory.PickupSound "misc/bullet_pkup"; Inventory.Amount 1; Inventory.PickupFlash "SWWMPickupFlash"; +FLOATBOB; FloatBobStrength 0.25; } virtual Class GetParentMagAmmo() { Class type = GetClass(); while ( (type.GetParentClass() != "MagAmmo") && type.GetParentClass() ) type = type.GetParentClass(); return (Class)(type); } private bool CmpAmmo( Class a, Class b ) { let amta = GetDefaultByType(a).Amount; let amtb = GetDefaultByType(b).Amount; return (amta < amtb); } private int partition_ammotypes( Array > a, int l, int h ) { Class pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpAmmo(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_ammotypes( Array > a, int l, int h ) { if ( l >= h ) return; int p = partition_ammotypes(a,l,h); qsort_ammotypes(a,l,p-1); qsort_ammotypes(a,p+1,h); } override string PickupMessage() { String tagstr = "$T_"..PickupTag; if ( Amount > 1 ) { tagstr = tagstr.."S"; return String.Format("%d %s",Amount,StringTable.Localize(tagstr)); } return StringTable.Localize(tagstr); } override bool HandlePickup( Inventory item ) { // drop excess mag ammo if ( (item is 'MagAmmo') && (MagAmmo(item).GetParentMagAmmo() == GetClass()) ) { if ( bUNDROPPABLE || bUNTOSSABLE ) { // undroppable mag ammo only drops full mags. // due to the way this works, we theoretically // should never end up with ammo "disappearing" while ( Amount+item.Amount >= MaxAmount ) { if ( Amount < ClipSize ) break; // first of all, see if we can INCREASE // parent ammo, rather than drop a mag if ( pamo.Amount < pamo.MaxAmount ) pamo.Amount++; else if ( !pamo.bUNDROPPABLE && !pamo.bUNTOSSABLE ) DoDrop(ParentAmmo); Amount -= ClipSize; } } else if ( pamo.Amount < pamo.MaxAmount ) { // see if we can fill mags with this, and drop the excess int toadd = Amount+item.Amount; while ( (pamo.Amount < pamo.MaxAmount) && (toadd >= ClipSize) ) { pamo.Amount++; toadd -= ClipSize; } Amount = toadd; if ( Amount > MaxAmount ) CreateTossable(Amount-MaxAmount); item.bPickupGood = true; return true; } else { int excess = Amount+item.Amount; if ( excess > MaxAmount ) excess -= MaxAmount; if ( excess < item.Amount ) { // enumerate all subclasses Array > ammotypes; ammotypes.Clear(); foreach ( cls:AllActorClasses ) { if ( cls is GetParentMagAmmo() ) ammotypes.Push((Class)(cls)); } // sort from largest to smallest qsort_ammotypes(ammotypes,0,ammotypes.Size()-1); // drop spares Inventory last; while ( excess > 0 ) { // drop full mag if possible if ( excess >= ClipSize ) { double ang = FRandom[Junk](0,360); last = DoDrop(ParentAmmo); last.SetOrigin(item.pos,false); last.vel.xy = AngleToVector(ang,FRandom[Junk](2,5)); excess -= ClipSize; continue; } // drop bullets otherwise foreach ( type:ammotypes ) { let def = GetDefaultByType(type); if ( excess >= def.Amount ) { double ang = FRandom[Junk](0,360); last = DoDrop(type); last.SetOrigin(item.pos,false); last.vel.xy = AngleToVector(ang,FRandom[Junk](2,5)); excess -= def.Amount; break; } } } } } if ( Amount < MaxAmount ) { int receiving = item.Amount; int oldamt = Amount; if ( (Amount > 0) && ((Amount+item.Amount) < 0) ) Amount = int.max; else Amount += item.Amount; if ( Amount > MaxAmount && !sv_unlimited_pickup ) Amount = MaxAmount; item.bPickupGood = true; // autoswitch if needed (checks parent ammo type) if ( (oldamt == 0) && Owner && Owner.player ) PlayerPawn(Owner).CheckWeaponSwitch(ParentAmmo); } return true; } return false; } override Inventory CreateCopy( Actor other ) { Inventory copy; int amount = Amount; let type = GetParentMagAmmo(); if ( (GetClass() != type) && type ) { if ( !GoAway() ) Destroy(); copy = Inventory(Spawn(type)); copy.Amount = amount; copy.BecomeItem(); } else { copy = Super.CreateCopy(other); copy.Amount = amount; } if ( copy.Amount > copy.MaxAmount ) copy.Amount = copy.MaxAmount; return copy; } private Inventory DoDrop( Class type ) { let copy = Inventory(Spawn(type,Owner.Pos,NO_REPLACE)); if ( !copy ) return null; copy.DropTime = 30; copy.bSpecial = copy.bSolid = false; copy.SetOrigin(Owner.Vec3Offset(0,0,10.),false); copy.Angle = Owner.Angle; copy.VelFromAngle(5.); copy.Vel.Z = 1.; copy.Vel += Owner.Vel; copy.bNoGravity = false; copy.ClearCounters(); copy.OnDrop(Owner); copy.vel += (RotateVector((FRandom[Junk](-1.5,.5),FRandom[Junk](-2.5,2.5)),Owner.angle),FRandom[Junk](2.,5.)); return copy; } override bool SpecialDropAction( Actor dropper ) { if ( swwm_enemydrops >= 0 ) { // random chance to not drop if ( Random[DropChance](1,100) <= Accuracy ) return true; if ( Amount == default.Amount ) return false; // subdivide Owner = dropper; // needed for positioning to work CreateTossable(Amount); return true; } // no ammo drops from enemies return true; } override void DoEffect() { Super.DoEffect(); if ( !pamo ) { pamo = Ammo(Owner.FindInventory(ParentAmmo)); if ( !pamo ) { pamo = Ammo(Spawn(ParentAmmo)); pamo.AttachToOwner(Owner); pamo.Amount = 0; } } // check if we can fill a mag if ( (Amount < ClipSize) || (pamo.Amount >= pamo.MaxAmount) ) return; MagFill(); } override void AttachToOwner( Actor other ) { Super.AttachToOwner(other); pamo = Ammo(other.FindInventory(ParentAmmo)); if ( !pamo ) { pamo = Ammo(Spawn(ParentAmmo)); pamo.AttachToOwner(other); pamo.Amount = 0; } } bool MagFill() { // double-check that parent ammo exists if ( !pamo ) { pamo = Ammo(Owner.FindInventory(ParentAmmo)); if ( !pamo ) { pamo = Ammo(Spawn(ParentAmmo)); pamo.AttachToOwner(Owner); pamo.Amount = 0; } } bool given = false; while ( (pamo.Amount < pamo.MaxAmount) && (Amount >= ClipSize) ) { pamo.Amount++; Amount -= ClipSize; given = true; } return given; } override inventory CreateTossable( int amt ) { if ( bUndroppable || bUntossable || !Owner || (Amount <= 0) || (amt == 0) ) return null; // cap amt = min(amount,amt); // enumerate all subclasses Array > ammotypes; ammotypes.Clear(); foreach ( cls:AllActorClasses ) { if ( cls is GetParentMagAmmo() ) ammotypes.Push((Class)(cls)); } // sort from largest to smallest qsort_ammotypes(ammotypes,0,ammotypes.Size()-1); // perform subdivision Inventory last = null; let pammo = GetDefaultByType(ParentAmmo); while ( amt > 0 ) { // drop full mag if possible if ( amt >= ClipSize ) { last = DoDrop(ParentAmmo); amt -= ClipSize; Amount -= ClipSize; continue; } // drop bullets otherwise foreach ( type:ammotypes ) { let def = GetDefaultByType(type); if ( amt >= def.Amount ) { last = DoDrop(type); amt -= def.Amount; Amount -= def.Amount; break; } } } return last; } override void ModifyDropAmount( int dropamount ) { Super.ModifyDropAmount(dropamount); int maxdrop = 1; foreach ( cls:AllActorClasses ) { if ( !(cls is GetParentMagAmmo()) ) continue; let def = GetDefaultByType((Class)(cls)); maxdrop = max(maxdrop,def.amount); } Amount = Random[ShellDrop](1,clamp(dropamount,1,maxdrop)); } }