From 82ffdd5e6b1bee9ee96b5278a1101bfcd4c91706 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@users.noreply.github.com>
Date: Sat, 24 Nov 2018 22:22:36 +0100
Subject: [PATCH] - moved the weapon selection logic to PlayerPawn as
 overridable virtual functions.

---
 src/g_game.cpp                              | 256 +++-----------------
 src/g_inventory/a_weapons.cpp               |  40 ++-
 src/g_inventory/a_weapons.h                 |   1 -
 wadsrc/static/zscript/inventory/weapons.txt |   2 +
 wadsrc/static/zscript/shared/player.txt     | 219 +++++++++++++++++
 5 files changed, 270 insertions(+), 248 deletions(-)

diff --git a/src/g_game.cpp b/src/g_game.cpp
index fca81cd30..60e309741 100644
--- a/src/g_game.cpp
+++ b/src/g_game.cpp
@@ -298,236 +298,22 @@ CCMD (turnspeeds)
 	}
 }
 
-
-//===========================================================================
-//
-// FWeaponSlot :: PickWeapon
-//
-// Picks a weapon from this slot. If no weapon is selected in this slot,
-// or the first weapon in this slot is selected, returns the last weapon.
-// Otherwise, returns the previous weapon in this slot. This means
-// precedence is given to the last weapon in the slot, which by convention
-// is probably the strongest. Does not return weapons you have no ammo
-// for or which you do not possess.
-//
-//===========================================================================
-
-AWeapon *PickWeapon(player_t *player, int slot, bool checkammo)
-{
-	int i, j;
-
-	if (player->mo == nullptr)
-	{
-		return nullptr;
-	}
-	int Size = player->weapons.SlotSize(slot);
-	// Does this slot even have any weapons?
-	if (Size == 0)
-	{
-		return player->ReadyWeapon;
-	}
-	if (player->ReadyWeapon != nullptr)
-	{
-		for (i = 0; (unsigned)i < Size; i++)
-		{
-			auto weapontype = player->weapons.GetWeapon(slot, i);
-			if (weapontype == player->ReadyWeapon->GetClass() ||
-				(player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP &&
-					player->ReadyWeapon->SisterWeapon != nullptr &&
-					player->ReadyWeapon->SisterWeapon->GetClass() == weapontype))
-			{
-				for (j = (i == 0 ? Size - 1 : i - 1);
-					j != i;
-					j = (j == 0 ? Size - 1 : j - 1))
-				{
-					auto weapontype2 = player->weapons.GetWeapon(slot, j);
-					AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory(weapontype2));
-
-					if (weap != nullptr && weap->IsKindOf(NAME_Weapon))
-					{
-						if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false))
-						{
-							return weap;
-						}
-					}
-				}
-			}
-		}
-	}
-	for (i = Size - 1; i >= 0; i--)
-	{
-		auto weapontype = player->weapons.GetWeapon(slot, i);
-		AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory(weapontype));
-
-		if (weap != nullptr && weap->IsKindOf(NAME_Weapon))
-		{
-			if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false))
-			{
-				return weap;
-			}
-		}
-	}
-	return player->ReadyWeapon;
-}
-
-//===========================================================================
-//
-// FindMostRecentWeapon
-//
-// Locates the slot and index for the most recently selected weapon. If the
-// player is in the process of switching to a new weapon, that is the most
-// recently selected weapon. Otherwise, the current weapon is the most recent
-// weapon.
-//
-//===========================================================================
-
-static bool FindMostRecentWeapon(player_t *player, int *slot, int *index)
-{
-	if (player->PendingWeapon != WP_NOCHANGE)
-	{
-		return player->weapons.LocateWeapon(player->PendingWeapon->GetClass(), slot, index);
-	}
-	else if (player->ReadyWeapon != nullptr)
-	{
-		AWeapon *weap = player->ReadyWeapon;
-		if (!player->weapons.LocateWeapon(weap->GetClass(), slot, index))
-		{
-			// If the current weapon wasn't found and is powered up,
-			// look for its non-powered up version.
-			if (weap->WeaponFlags & WIF_POWERED_UP && weap->SisterWeaponType != nullptr)
-			{
-				return player->weapons.LocateWeapon(weap->SisterWeaponType, slot, index);
-			}
-			return false;
-		}
-		return true;
-	}
-	else
-	{
-		return false;
-	}
-}
-
-//===========================================================================
-//
-// FWeaponSlots :: PickNextWeapon
-//
-// Returns the "next" weapon for this player. If the current weapon is not
-// in a slot, then it just returns that weapon, since there's nothing to
-// consider it relative to.
-//
-//===========================================================================
-
-AWeapon *PickNextWeapon(player_t *player)
-{
-	int startslot, startindex;
-	int slotschecked = 0;
-
-	if (player->mo == nullptr)
-	{
-		return nullptr;
-	}
-	if (player->ReadyWeapon == nullptr || FindMostRecentWeapon(player, &startslot, &startindex))
-	{
-		int slot;
-		int index;
-
-		if (player->ReadyWeapon == nullptr)
-		{
-			startslot = NUM_WEAPON_SLOTS - 1;
-			startindex = player->weapons.SlotSize(startslot) - 1;
-		}
-
-		slot = startslot;
-		index = startindex;
-		do
-		{
-			if (++index >= player->weapons.SlotSize(slot))
-			{
-				index = 0;
-				slotschecked++;
-				if (++slot >= NUM_WEAPON_SLOTS)
-				{
-					slot = 0;
-				}
-			}
-			PClassActor *type = player->weapons.GetWeapon(slot, index);
-			AWeapon *weap = static_cast<AWeapon *>(player->mo->FindInventory(type));
-			if (weap != nullptr && weap->CheckAmmo(AWeapon::EitherFire, false))
-			{
-				return weap;
-			}
-		} while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
-	}
-	return player->ReadyWeapon;
-}
-
-//===========================================================================
-//
-// FWeaponSlots :: PickPrevWeapon
-//
-// Returns the "previous" weapon for this player. If the current weapon is
-// not in a slot, then it just returns that weapon, since there's nothing to
-// consider it relative to.
-//
-//===========================================================================
-
-AWeapon *PickPrevWeapon(player_t *player)
-{
-	int startslot, startindex;
-	int slotschecked = 0;
-
-	if (player->mo == nullptr)
-	{
-		return nullptr;
-	}
-	if (player->ReadyWeapon == nullptr || FindMostRecentWeapon(player, &startslot, &startindex))
-	{
-		int slot;
-		int index;
-
-		if (player->ReadyWeapon == nullptr)
-		{
-			startslot = 0;
-			startindex = 0;
-		}
-
-		slot = startslot;
-		index = startindex;
-		do
-		{
-			if (--index < 0)
-			{
-				slotschecked++;
-				if (--slot < 0)
-				{
-					slot = NUM_WEAPON_SLOTS - 1;
-				}
-				index = player->weapons.SlotSize(slot) - 1;
-			}
-			PClassActor *type = player->weapons.GetWeapon(slot, index);
-			AWeapon *weap = static_cast<AWeapon *>(player->mo->FindInventory(type));
-			if (weap != nullptr && weap->CheckAmmo(AWeapon::EitherFire, false))
-			{
-				return weap;
-			}
-		} while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
-	}
-	return player->ReadyWeapon;
-}
-
-
-
 CCMD (slot)
 {
 	if (argv.argc() > 1)
 	{
 		int slot = atoi (argv[1]);
 
-		if (slot < NUM_WEAPON_SLOTS)
+		auto mo = players[consoleplayer].mo;
+		if (slot < NUM_WEAPON_SLOTS && mo)
 		{
 			// Needs to be redone
-			SendItemUse = PickWeapon(&players[consoleplayer], slot, !(dmflags2 & DF2_DONTCHECKAMMO));
+			IFVIRTUALPTR(mo, APlayerPawn, PickWeapon)
+			{
+				VMValue param[] = { mo, slot, !(dmflags2 & DF2_DONTCHECKAMMO) };
+				VMReturn ret((void**)&SendItemUse);
+				VMCall(func, param, 3, &ret, 1);
+			}
 		}
 	}
 }
@@ -559,7 +345,18 @@ CCMD (turn180)
 
 CCMD (weapnext)
 {
-	SendItemUse = PickNextWeapon (&players[consoleplayer]);
+	auto mo = players[consoleplayer].mo;
+	if (mo)
+	{
+		// Needs to be redone
+		IFVIRTUALPTR(mo, APlayerPawn, PickNextWeapon)
+		{
+			VMValue param[] = { mo };
+			VMReturn ret((void**)&SendItemUse);
+			VMCall(func, param, 1, &ret, 1);
+		}
+	}
+
 	// [BC] Option to display the name of the weapon being cycled to.
 	if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse)
 	{
@@ -574,7 +371,18 @@ CCMD (weapnext)
 
 CCMD (weapprev)
 {
-	SendItemUse = PickPrevWeapon (&players[consoleplayer]);
+	auto mo = players[consoleplayer].mo;
+	if (mo)
+	{
+		// Needs to be redone
+		IFVIRTUALPTR(mo, APlayerPawn, PickPrevWeapon)
+		{
+			VMValue param[] = { mo };
+			VMReturn ret((void**)&SendItemUse);
+			VMCall(func, param, 1, &ret, 1);
+		}
+	}
+
 	// [BC] Option to display the name of the weapon being cycled to.
 	if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse)
 	{
diff --git a/src/g_inventory/a_weapons.cpp b/src/g_inventory/a_weapons.cpp
index 5b98a074f..d4826b243 100644
--- a/src/g_inventory/a_weapons.cpp
+++ b/src/g_inventory/a_weapons.cpp
@@ -211,29 +211,6 @@ void AWeapon::MarkPrecacheSounds() const
 	ReadySound.MarkUsed();
 }
 
-//===========================================================================
-//
-// AWeapon :: CheckAmmo
-//
-// Returns true if there is enough ammo to shoot.  If not, selects the
-// next weapon to use.
-//
-//===========================================================================
-
-bool AWeapon::CheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo, int ammocount)
-{
-	IFVIRTUAL(AWeapon, CheckAmmo)
-	{
-		VMValue params[] = { (DObject*)this, fireMode, autoSwitch, requireAmmo, ammocount };
-		VMReturn ret;
-		int retval;
-		ret.IntAt(&retval);
-		VMCall(func, params, 5, &ret, 1);
-		return !!retval;
-	}
-	return false;
-}
-
 /* Weapon slots ***********************************************************/
 
 //===========================================================================
@@ -473,6 +450,23 @@ DEFINE_ACTION_FUNCTION(FWeaponSlots, LocateWeapon)
 	return MIN(numret, 3);
 }
 
+DEFINE_ACTION_FUNCTION(FWeaponSlots, GetWeapon)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FWeaponSlots);
+	PARAM_INT(slot);
+	PARAM_INT(index);
+	ACTION_RETURN_POINTER(self->GetWeapon(slot, index));
+	return 1;
+}
+
+DEFINE_ACTION_FUNCTION(FWeaponSlots, SlotSize)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FWeaponSlots);
+	PARAM_INT(slot);
+	ACTION_RETURN_INT(self->SlotSize(slot));
+	return 1;
+}
+
 //===========================================================================
 //
 // FWeaponSlots :: AddExtraWeapons
diff --git a/src/g_inventory/a_weapons.h b/src/g_inventory/a_weapons.h
index 1c4109425..f1252f729 100644
--- a/src/g_inventory/a_weapons.h
+++ b/src/g_inventory/a_weapons.h
@@ -174,7 +174,6 @@ public:
 		AltFire,
 		EitherFire
 	};
-	bool CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo=false, int ammocount = -1);
 
 	enum
 	{
diff --git a/wadsrc/static/zscript/inventory/weapons.txt b/wadsrc/static/zscript/inventory/weapons.txt
index 9f4196de2..8612af792 100644
--- a/wadsrc/static/zscript/inventory/weapons.txt
+++ b/wadsrc/static/zscript/inventory/weapons.txt
@@ -961,4 +961,6 @@ struct WeaponSlots native
 {
 	native bool, int, int LocateWeapon(class<Weapon> weap);
 	native static void SetupWeaponSlots(PlayerPawn pp);
+	native class<Weapon> GetWeapon(int slot, int index);
+	native int SlotSize(int slot);
 }
diff --git a/wadsrc/static/zscript/shared/player.txt b/wadsrc/static/zscript/shared/player.txt
index 478d3e813..2e1cda0e9 100644
--- a/wadsrc/static/zscript/shared/player.txt
+++ b/wadsrc/static/zscript/shared/player.txt
@@ -1721,6 +1721,225 @@ class PlayerPawn : Actor native
 		return TeleportFreezeTime;
 	}
 	
+	//===========================================================================
+	//
+	// FWeaponSlot :: PickWeapon
+	//
+	// Picks a weapon from this slot. If no weapon is selected in this slot,
+	// or the first weapon in this slot is selected, returns the last weapon.
+	// Otherwise, returns the previous weapon in this slot. This means
+	// precedence is given to the last weapon in the slot, which by convention
+	// is probably the strongest. Does not return weapons you have no ammo
+	// for or which you do not possess.
+	//
+	//===========================================================================
+
+	virtual Weapon PickWeapon(int slot, bool checkammo)
+	{
+		int i, j;
+
+		let player = self.player;
+		int Size = player.weapons.SlotSize(slot);
+		// Does this slot even have any weapons?
+		if (Size == 0)
+		{
+			return player.ReadyWeapon;
+		}
+		let ReadyWeapon = player.ReadyWeapon;
+		if (ReadyWeapon != null)
+		{
+			for (i = 0; i < Size; i++)
+			{
+				let weapontype = player.weapons.GetWeapon(slot, i);
+				if (weapontype == ReadyWeapon.GetClass() ||
+					(ReadyWeapon.bPOWERED_UP && ReadyWeapon.SisterWeapon != null && ReadyWeapon.SisterWeapon.GetClass() == weapontype))
+				{
+					for (j = (i == 0 ? Size - 1 : i - 1);
+						j != i;
+						j = (j == 0 ? Size - 1 : j - 1))
+					{
+						let weapontype2 = player.weapons.GetWeapon(slot, j);
+						let weap = Weapon(player.mo.FindInventory(weapontype2));
+
+						if (weap != null)
+						{
+							if (!checkammo || weap.CheckAmmo(Weapon.EitherFire, false))
+							{
+								return weap;
+							}
+						}
+					}
+				}
+			}
+		}
+		for (i = Size - 1; i >= 0; i--)
+		{
+			let weapontype = player.weapons.GetWeapon(slot, i);
+			let weap = Weapon(player.mo.FindInventory(weapontype));
+
+			if (weap != null)
+			{
+				if (!checkammo || weap.CheckAmmo(Weapon.EitherFire, false))
+				{
+					return weap;
+				}
+			}
+		}
+		return ReadyWeapon;
+	}
+
+	//===========================================================================
+	//
+	// FindMostRecentWeapon
+	//
+	// Locates the slot and index for the most recently selected weapon. If the
+	// player is in the process of switching to a new weapon, that is the most
+	// recently selected weapon. Otherwise, the current weapon is the most recent
+	// weapon.
+	//
+	//===========================================================================
+
+	bool, int, int FindMostRecentWeapon()
+	{
+		let player = self.player;
+		let ReadyWeapon = player.ReadyWeapon;
+		if (player.PendingWeapon != WP_NOCHANGE)
+		{
+			return player.weapons.LocateWeapon(player.PendingWeapon.GetClass());
+		}
+		else if (ReadyWeapon != null)
+		{
+			bool found;
+			int slot;
+			int index;
+			[found, slot, index] = player.weapons.LocateWeapon(ReadyWeapon.GetClass());
+			if (!found)
+			{
+				// If the current weapon wasn't found and is powered up,
+				// look for its non-powered up version.
+				if (ReadyWeapon.bPOWERED_UP && ReadyWeapon.SisterWeaponType != null)
+				{
+					return player.weapons.LocateWeapon(ReadyWeapon.SisterWeaponType);
+				}
+				return false, 0, 0;
+			}
+			return true, slot, index;
+		}
+		else
+		{
+			return false, 0, 0;
+		}
+	}
+
+	//===========================================================================
+	//
+	// FWeaponSlots :: PickNextWeapon
+	//
+	// Returns the "next" weapon for this player. If the current weapon is not
+	// in a slot, then it just returns that weapon, since there's nothing to
+	// consider it relative to.
+	//
+	//===========================================================================
+	const NUM_WEAPON_SLOTS = 10;
+
+	virtual Weapon PickNextWeapon()
+	{
+		let player = self.player;
+		bool found;
+		int startslot, startindex;
+		int slotschecked = 0;
+
+		[found, startslot, startindex] = FindMostRecentWeapon();
+		let ReadyWeapon = player.ReadyWeapon;
+		if (ReadyWeapon == null || found)
+		{
+			int slot;
+			int index;
+
+			if (ReadyWeapon == null)
+			{
+				startslot = NUM_WEAPON_SLOTS - 1;
+				startindex = player.weapons.SlotSize(startslot) - 1;
+			}
+
+			slot = startslot;
+			index = startindex;
+			do
+			{
+				if (++index >= player.weapons.SlotSize(slot))
+				{
+					index = 0;
+					slotschecked++;
+					if (++slot >= NUM_WEAPON_SLOTS)
+					{
+						slot = 0;
+					}
+				}
+				let type = player.weapons.GetWeapon(slot, index);
+				let weap = Weapon(FindInventory(type));
+				if (weap != null && weap.CheckAmmo(Weapon.EitherFire, false))
+				{
+					return weap;
+				}
+			} while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
+		}
+		return ReadyWeapon;
+	}
+
+	//===========================================================================
+	//
+	// FWeaponSlots :: PickPrevWeapon
+	//
+	// Returns the "previous" weapon for this player. If the current weapon is
+	// not in a slot, then it just returns that weapon, since there's nothing to
+	// consider it relative to.
+	//
+	//===========================================================================
+
+	virtual Weapon PickPrevWeapon()
+	{
+		let player = self.player;
+		int startslot, startindex;
+		bool found;
+		int slotschecked = 0;
+
+		[found, startslot, startindex] = FindMostRecentWeapon();
+		if (player.ReadyWeapon == null || found)
+		{
+			int slot;
+			int index;
+
+			if (player.ReadyWeapon == null)
+			{
+				startslot = 0;
+				startindex = 0;
+			}
+
+			slot = startslot;
+			index = startindex;
+			do
+			{
+				if (--index < 0)
+				{
+					slotschecked++;
+					if (--slot < 0)
+					{
+						slot = NUM_WEAPON_SLOTS - 1;
+					}
+					index = player.weapons.SlotSize(slot) - 1;
+				}
+				let type = player.weapons.GetWeapon(slot, index);
+				let weap = Weapon(FindInventory(type));
+				if (weap != null && weap.CheckAmmo(Weapon.EitherFire, false))
+				{
+					return weap;
+				}
+			} while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
+		}
+		return player.ReadyWeapon;
+	}
+
+	
 	//----------------------------------------------------------------------------
 	//
 	//