- Fuck it, Quadravol will be lever action. - Tiny cleanup. - Rewrite the weapon replacement system (less of a mess now maybe?). - Some menu fixes. - Minimap zoom increments like in the Common Library. - Add missing sound definition for Safety Tether. (This mostly went unnoticed because it's VERY rare to have it play) - Shift Sparkster x3 (DLC2) to slot 7. This way you can have both it and the Quadravol simultaneously. It would be unfair to not let you hold both "iconic" UnSX weapons at once. - Small lore tweak on Quadravol stance swap. - Fix off-by-one bug in looping palette lights. - Re-do logo shader. Use separate layer textures. - Fix Elemental Coating breaking "End Level" damage sectors. (This will be the last batch of changes before I continue working on menus)
610 lines
14 KiB
Text
610 lines
14 KiB
Text
// Common code for ammo division
|
|
Class SWWMAmmo : Ammo
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
|
|
String PickupTag;
|
|
Class<MagAmmo> MagAmmoType;
|
|
private int SAmmoFlags;
|
|
transient bool bSinglePrint; // used for pickup messages of mag manager
|
|
|
|
Property PickupTag : PickupTag;
|
|
Property MagAmmoType : MagAmmoType;
|
|
|
|
FlagDef UsePickupMsg : SAmmoFlags, 0; // use the set pickup message rather than generating from pickup tag
|
|
|
|
override Class<Ammo> GetParentAmmo()
|
|
{
|
|
Class<Object> type = GetClass();
|
|
while ( (type.GetParentClass() != "SWWMAmmo") && type.GetParentClass() )
|
|
type = type.GetParentClass();
|
|
return (Class<Ammo>)(type);
|
|
}
|
|
|
|
override string PickupMessage()
|
|
{
|
|
if ( bUsePickupMsg ) return Super.PickupMessage();
|
|
String tagstr = "$T_"..PickupTag;
|
|
if ( (Amount > 1) && !bSinglePrint )
|
|
{
|
|
tagstr = tagstr.."S";
|
|
return String.Format("%d %s",Amount,StringTable.Localize(tagstr));
|
|
}
|
|
bSinglePrint = false;
|
|
return StringTable.Localize(tagstr);
|
|
}
|
|
|
|
private Inventory DoDrop( Class<Inventory> 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<Ammo> a, Class<Ammo> b )
|
|
{
|
|
let amta = GetDefaultByType(a).Amount;
|
|
let amtb = GetDefaultByType(b).Amount;
|
|
return (amta < amtb);
|
|
}
|
|
|
|
private int partition_ammotypes( Array<Class<Ammo> > a, int l, int h )
|
|
{
|
|
Class<Ammo> pv = a[h];
|
|
int i = (l-1);
|
|
for ( int j=l; j<=(h-1); j++ )
|
|
{
|
|
if ( CmpAmmo(pv,a[j]) )
|
|
{
|
|
i++;
|
|
Class<Ammo> tmp = a[j];
|
|
a[j] = a[i];
|
|
a[i] = tmp;
|
|
}
|
|
}
|
|
Class<Ammo> tmp = a[h];
|
|
a[h] = a[i+1];
|
|
a[i+1] = tmp;
|
|
return i+1;
|
|
}
|
|
private void qsort_ammotypes( Array<Class<Ammo> > 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<Class<Ammo> > ammotypes;
|
|
ammotypes.Clear();
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
if ( AllActorClasses[i] is GetParentAmmo() )
|
|
ammotypes.Push((Class<Ammo>)(AllActorClasses[i]));
|
|
}
|
|
// sort from largest to smallest
|
|
qsort_ammotypes(ammotypes,0,ammotypes.Size()-1);
|
|
// perform subdivision
|
|
Inventory last = null;
|
|
while ( amt > 0 )
|
|
{
|
|
for ( int i=0; i<ammotypes.Size(); i++ )
|
|
{
|
|
let def = GetDefaultByType(ammotypes[i]);
|
|
if ( amt >= def.Amount )
|
|
{
|
|
last = DoDrop(ammotypes[i]);
|
|
amt -= def.Amount;
|
|
Amount -= def.Amount;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return last;
|
|
}
|
|
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
// drop excess ammo
|
|
if ( (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<Class<Ammo> > ammotypes;
|
|
ammotypes.Clear();
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
if ( AllActorClasses[i] is GetParentAmmo() )
|
|
ammotypes.Push((Class<Ammo>)(AllActorClasses[i]));
|
|
}
|
|
// sort from largest to smallest
|
|
qsort_ammotypes(ammotypes,0,ammotypes.Size()-1);
|
|
// drop spares
|
|
Inventory last;
|
|
while ( excess > 0 )
|
|
{
|
|
for ( int i=0; i<ammotypes.Size(); i++ )
|
|
{
|
|
let def = GetDefaultByType(ammotypes[i]);
|
|
if ( excess >= def.Amount )
|
|
{
|
|
double ang = FRandom[Junk](0,360);
|
|
last = DoDrop(ammotypes[i]);
|
|
last.SetOrigin(item.pos,false);
|
|
last.vel.xy = (cos(ang),sin(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 ( ma.Amount < ma.MaxAmount )
|
|
{
|
|
// split into bullets
|
|
for ( int i=0; i<item.Amount; i++ )
|
|
{
|
|
int bul = ma.ClipSize;
|
|
int maxgiveamt = min(ma.MaxAmount-ma.Amount,bul);
|
|
int dropamt = bul-maxgiveamt;
|
|
if ( dropamt > 0 ) ma.CreateTossable(dropamt);
|
|
if ( (bul == ma.ClipSize) && (Amount < MaxAmount) ) Amount++;
|
|
else ma.Amount = min(ma.MaxAmount,ma.Amount+bul);
|
|
}
|
|
item.bPickupGood = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
|
|
override bool CanPickup( Actor toucher )
|
|
{
|
|
// don't allow picking up ammo for weapons we can't pick up
|
|
if ( !Super.CanPickup(toucher) ) return false;
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let w = (Class<Weapon>)(AllActorClasses[i]);
|
|
if ( !w ) continue;
|
|
if ( w is 'SWWMWeapon' )
|
|
{
|
|
let def = GetDefaultByType((Class<SWWMWeapon>)(w));
|
|
if ( !def.UsesAmmo(GetClass()) ) continue;
|
|
if ( !def.CanPickup(toucher) ) return false;
|
|
}
|
|
else
|
|
{
|
|
let def = GetDefaultByType(w);
|
|
if ( (def.AmmoType1 != GetClass()) && (def.AmmoType2 != GetClass()) ) continue;
|
|
if ( !def.CanPickup(toucher) ) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
if ( !(AllActorClasses[i] is GetParentAmmo()) ) continue;
|
|
let def = GetDefaultByType((Class<Ammo>)(AllActorClasses[i]));
|
|
maxdrop = max(maxdrop,def.amount);
|
|
}
|
|
Amount = Random[ShellDrop](1,clamp(dropamount,1,maxdrop));
|
|
}
|
|
|
|
default
|
|
{
|
|
+INVENTORY.IGNORESKILL;
|
|
Inventory.PickupFlash "SWWMPickupFlash";
|
|
}
|
|
}
|
|
|
|
// Common code for individual bullets
|
|
Class MagAmmo : Inventory abstract
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
|
|
Class<Ammo> ParentAmmo;
|
|
Ammo pamo;
|
|
int ClipSize;
|
|
int countdown;
|
|
String PickupTag;
|
|
transient bool bSinglePrint; // used for pickup messages of mag manager
|
|
|
|
Property ParentAmmo : ParentAmmo;
|
|
Property ClipSize : ClipSize;
|
|
Property PickupTag : PickupTag;
|
|
|
|
default
|
|
{
|
|
+INVENTORY.KEEPDEPLETED;
|
|
Inventory.PickupSound "misc/bullet_pkup";
|
|
Inventory.Amount 1;
|
|
Inventory.PickupFlash "SWWMPickupFlash";
|
|
}
|
|
|
|
virtual Class<MagAmmo> GetParentMagAmmo()
|
|
{
|
|
Class<Object> type = GetClass();
|
|
while ( (type.GetParentClass() != "MagAmmo") && type.GetParentClass() )
|
|
type = type.GetParentClass();
|
|
return (Class<MagAmmo>)(type);
|
|
}
|
|
|
|
private bool CmpAmmo( Class<MagAmmo> a, Class<MagAmmo> b )
|
|
{
|
|
let amta = GetDefaultByType(a).Amount;
|
|
let amtb = GetDefaultByType(b).Amount;
|
|
return (amta < amtb);
|
|
}
|
|
|
|
private int partition_ammotypes( Array<Class<MagAmmo> > a, int l, int h )
|
|
{
|
|
Class<MagAmmo> pv = a[h];
|
|
int i = (l-1);
|
|
for ( int j=l; j<=(h-1); j++ )
|
|
{
|
|
if ( CmpAmmo(pv,a[j]) )
|
|
{
|
|
i++;
|
|
Class<MagAmmo> tmp = a[j];
|
|
a[j] = a[i];
|
|
a[i] = tmp;
|
|
}
|
|
}
|
|
Class<MagAmmo> tmp = a[h];
|
|
a[h] = a[i+1];
|
|
a[i+1] = tmp;
|
|
return i+1;
|
|
}
|
|
private void qsort_ammotypes( Array<Class<MagAmmo> > 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) && !bSinglePrint )
|
|
{
|
|
tagstr = tagstr.."S";
|
|
return String.Format("%d %s",Amount,StringTable.Localize(tagstr));
|
|
}
|
|
bSinglePrint = false;
|
|
return StringTable.Localize(tagstr);
|
|
}
|
|
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
// drop excess mag ammo
|
|
if ( (item is 'MagAmmo') && (MagAmmo(item).GetParentMagAmmo() == GetClass()) )
|
|
{
|
|
int excess = Amount+item.Amount;
|
|
if ( excess > MaxAmount ) excess -= MaxAmount;
|
|
if ( excess < item.Amount )
|
|
{
|
|
// enumerate all subclasses
|
|
Array<Class<MagAmmo> > ammotypes;
|
|
ammotypes.Clear();
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
if ( AllActorClasses[i] is GetParentMagAmmo() )
|
|
ammotypes.Push((Class<MagAmmo>)(AllActorClasses[i]));
|
|
}
|
|
// 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 = (cos(ang),sin(ang))*FRandom[Junk](2,5);
|
|
excess -= ClipSize;
|
|
continue;
|
|
}
|
|
// drop bullets otherwise
|
|
for ( int i=0; i<ammotypes.Size(); i++ )
|
|
{
|
|
let def = GetDefaultByType(ammotypes[i]);
|
|
if ( excess >= def.Amount )
|
|
{
|
|
double ang = FRandom[Junk](0,360);
|
|
last = DoDrop(ammotypes[i]);
|
|
last.SetOrigin(item.pos,false);
|
|
last.vel.xy = (cos(ang),sin(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;
|
|
}
|
|
|
|
override bool CanPickup( Actor toucher )
|
|
{
|
|
// don't allow picking up ammo for weapons we can't pick up
|
|
if ( !Super.CanPickup(toucher) ) return false;
|
|
let def = GetDefaultByType(ParentAmmo);
|
|
return def.CanPickup(toucher);
|
|
}
|
|
|
|
private Inventory DoDrop( Class<Inventory> 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 (delayed)
|
|
if ( (Amount < ClipSize) || (pamo.Amount >= pamo.MaxAmount) )
|
|
{
|
|
countdown = 35;
|
|
return;
|
|
}
|
|
if ( countdown-- > 0 ) return;
|
|
MagFill();
|
|
}
|
|
|
|
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;
|
|
if ( Owner.CheckLocalView() )
|
|
{
|
|
SWWMAmmo(pamo).bSinglePrint = true;
|
|
pamo.PrintPickupMessage(true,pamo.PickupMessage());
|
|
}
|
|
}
|
|
if ( given ) pamo.PlayPickupSound(Owner);
|
|
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<Class<MagAmmo> > ammotypes;
|
|
ammotypes.Clear();
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
if ( AllActorClasses[i] is GetParentMagAmmo() )
|
|
ammotypes.Push((Class<MagAmmo>)(AllActorClasses[i]));
|
|
}
|
|
// 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
|
|
for ( int i=0; i<ammotypes.Size(); i++ )
|
|
{
|
|
let def = GetDefaultByType(ammotypes[i]);
|
|
if ( amt >= def.Amount )
|
|
{
|
|
last = DoDrop(ammotypes[i]);
|
|
amt -= def.Amount;
|
|
Amount -= def.Amount;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return last;
|
|
}
|
|
|
|
override void ModifyDropAmount( int dropamount )
|
|
{
|
|
Super.ModifyDropAmount(dropamount);
|
|
Amount = min(Random[ShellDrop](1,ClipSize),Amount);
|
|
}
|
|
}
|
|
|
|
// Ref class for ammo spawners, used by on-demand replacers
|
|
Class SWWMAmmoSpawner : Inventory abstract
|
|
{
|
|
virtual void SpawnAmmo() {}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
SpawnAmmo();
|
|
ClearCounters();
|
|
Destroy();
|
|
}
|
|
|
|
override bool CanPickup( Actor toucher )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
override bool TryPickup( in out Actor toucher )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+NOINTERACTION;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
-SPECIAL;
|
|
}
|
|
}
|