#include <string.h>

#include "a_pickups.h"
#include "gi.h"
#include "d_player.h"
#include "s_sound.h"
#include "i_system.h"
#include "r_state.h"
#include "p_pspr.h"
#include "c_dispatch.h"
#include "m_misc.h"
#include "gameconfigfile.h"
#include "cmdlib.h"
#include "templates.h"
#include "sbar.h"
#include "deathmatch.h"
#include "team.h"
#include "cl_commands.h"
#include "network.h"
#include "sv_commands.h"

#define BONUSADD 6

FState AWeapon::States[] =
{
	S_NORMAL (SHTG, 'E',	0, A_Light0 			, NULL)
};

IMPLEMENT_POINTY_CLASS (AWeapon)
 DECLARE_POINTER (Ammo1)
 DECLARE_POINTER (Ammo2)
 DECLARE_POINTER (SisterWeapon)
END_POINTERS

BEGIN_DEFAULTS (AWeapon, Any, -1, 0)
 PROP_Inventory_PickupSound ("misc/w_pkup")
END_DEFAULTS

//===========================================================================
//
// AWeapon :: Serialize
//
//===========================================================================

void AWeapon::Serialize (FArchive &arc)
{
	Super::Serialize (arc);
	arc << WeaponFlags
		<< AmmoType1 << AmmoType2
		<< AmmoGive1 << AmmoGive2
		<< MinAmmo1 << MinAmmo2
		<< AmmoUse1 << AmmoUse2
		// [BC] Also archive the amount of ammo used in DM.
//		<< AmmoUseDM1 << AmmoUseDM2
		<< Kickback
		<< YAdjust
		<< AR_SOUNDW(UpSound) << AR_SOUNDW(ReadySound)
		<< SisterWeaponType
		<< ProjectileType << AltProjectileType
		<< SelectionOrder
		<< MoveCombatDist
		<< UpState << DownState << ReadyState
		<< AtkState << HoldAtkState
		<< AltAtkState << AltHoldAtkState
		<< FlashState << AltFlashState
		<< Ammo1 << Ammo2 << SisterWeapon
		<< bAltFire;
}

//===========================================================================
//
// AWeapon :: TryPickup
//
// If you can't see the weapon when it's active, then you can't pick it up.
//
//===========================================================================

bool AWeapon::TryPickup (AActor *toucher)
{
	if (ReadyState != NULL &&
		ReadyState->GetFrame() < sprites[ReadyState->sprite.index].numframes)
	{
		return Super::TryPickup (toucher);
	}
	return false;
}

//===========================================================================
//
// AWeapon :: Use
//
// Make the player switch to this weapon.
//
//===========================================================================

bool AWeapon::Use (bool pickup)
{
	AWeapon *useweap = this;

	// Powered up weapons cannot be used directly.
	if (WeaponFlags & WIF_POWERED_UP) return false;

	// If the player is powered-up, use the alternate version of the
	// weapon, if one exists.
	if (SisterWeapon != NULL &&
		SisterWeapon->WeaponFlags & WIF_POWERED_UP &&
		Owner->FindInventory (RUNTIME_CLASS(APowerWeaponLevel2)))
	{
		useweap = SisterWeapon;
	}
	if (Owner->player != NULL && Owner->player->ReadyWeapon != useweap)
	{
		Owner->player->PendingWeapon = useweap;

		// [BC] If we're a client, tell the server we're switching weapons.
		if (( NETWORK_GetState( ) == NETSTATE_CLIENT ) && (( Owner->player - players ) == consoleplayer ))
			CLIENTCOMMANDS_WeaponSelect( ( char *)useweap->GetClass( )->TypeName.GetChars( ));
	}
	// Return false so that the weapon is not removed from the inventory.
	return false;
}

//===========================================================================
//
// AWeapon :: HandlePickup
//
// Try to leach ammo from the weapon if you have it already.
//
//===========================================================================

bool AWeapon::HandlePickup (AInventory *item)
{
	if (item->GetClass() == GetClass())
	{
		if (static_cast<AWeapon *>(item)->PickupForAmmo (this))
		{
			item->ItemFlags |= IF_PICKUPGOOD;
		}
		return true;
	}
	if (Inventory != NULL)
	{
		return Inventory->HandlePickup (item);
	}
	return false;
}

//===========================================================================
//
// AWeapon :: PickupForAmmo
//
// The player already has this weapon, so try to pick it up for ammo.
//
//===========================================================================

bool AWeapon::PickupForAmmo (AWeapon *ownedWeapon)
{
	bool gotstuff = false;

	// Don't take ammo if the weapon sticks around.
	if (!ShouldStay ())
	{
		if (AmmoGive1 > 0)
		{
			gotstuff = AddExistingAmmo (ownedWeapon->Ammo1, AmmoGive1);

			// [BC] If we're the server, tell clients that they just received ammo.
			if (( NETWORK_GetState( ) == NETSTATE_SERVER ) && ( ownedWeapon->Owner ) && ( ownedWeapon->Owner->player ) && ( ownedWeapon->Ammo1 ))
				SERVERCOMMANDS_GiveInventory( ownedWeapon->Owner->player - players, static_cast<AInventory *>( ownedWeapon->Ammo1 ));
		}

		if (AmmoGive2 > 0)
		{
			gotstuff |= AddExistingAmmo (ownedWeapon->Ammo2, AmmoGive2);

			// [BC] If we're the server, tell clients that they just received ammo.
			if (( NETWORK_GetState( ) == NETSTATE_SERVER ) && ( ownedWeapon->Owner ) && ( ownedWeapon->Owner->player ) && ( ownedWeapon->Ammo2 ))
				SERVERCOMMANDS_GiveInventory( ownedWeapon->Owner->player - players, static_cast<AInventory *>( ownedWeapon->Ammo2 ));
		}
	}
	return gotstuff;
}

//===========================================================================
//
// AWeapon :: CreateCopy
//
//===========================================================================

AInventory *AWeapon::CreateCopy (AActor *other)
{
	AWeapon *copy = static_cast<AWeapon*>(Super::CreateCopy (other));
	if (copy != this)
	{
		copy->AmmoGive1 = AmmoGive1;
		copy->AmmoGive2 = AmmoGive2;
	}
	return copy;
}

//===========================================================================
//
// AWeapon :: CreateTossable
//
// A weapon that's tossed out should contain no ammo, so you can't cheat
// by dropping it and then picking it back up.
//
//===========================================================================

AInventory *AWeapon::CreateTossable ()
{
	// Only drop the weapon that is meant to be placed in a level. That is,
	// only drop the weapon that normally gives you ammo.
	if (SisterWeapon != NULL && 
		((AWeapon*)GetDefault())->AmmoGive1 == 0 &&
		((AWeapon*)GetDefault())->AmmoGive2 == 0 &&
		(((AWeapon*)SisterWeapon->GetDefault())->AmmoGive1 > 0 ||
		 ((AWeapon*)SisterWeapon->GetDefault())->AmmoGive2 > 0))
	{
		return SisterWeapon->CreateTossable ();
	}
	AWeapon *copy = static_cast<AWeapon *> (Super::CreateTossable ());

	if (copy != NULL)
	{
		// If this weapon has a sister, remove it from the inventory too.
		if (SisterWeapon != NULL)
		{
			SisterWeapon->Destroy ();
		}
		// To avoid exploits, the tossed weapon must not have any ammo.
		copy->AmmoGive1 = 0;
		copy->AmmoGive2 = 0;
	}
	return copy;
}

//===========================================================================
//
// AWeapon :: AttachToOwner
//
//===========================================================================

void AWeapon::AttachToOwner (AActor *other)
{
	// [BC]
	AWeapon		*pCompareWeapon;

	Super::AttachToOwner (other);

	Ammo1 = AddAmmo (Owner, AmmoType1, AmmoGive1);
	Ammo2 = AddAmmo (Owner, AmmoType2, AmmoGive2);
	SisterWeapon = AddWeapon (SisterWeaponType);
	if (Owner->player != NULL)
	{
		// [BC] Decide which weapon to compare selection orders against.
		if ( Owner->player->PendingWeapon != WP_NOCHANGE )
			pCompareWeapon = Owner->player->PendingWeapon;
		else
			pCompareWeapon = Owner->player->ReadyWeapon;

		// [BC] Handle the "switchonpickup" userinfo cvar. If it's >= 2, then
		// we always want to switch our weapon when we pickup a new one.
		if (Owner->player->userinfo.switchonpickup >= 2)
		{
			Owner->player->PendingWeapon = this;

			// [BC] If we're a client, tell the server we're switching weapons.
			if (( NETWORK_GetState( ) == NETSTATE_CLIENT ) && (( Owner->player - players ) == consoleplayer ))
				CLIENTCOMMANDS_WeaponSelect( (char *)this->GetClass( )->TypeName.GetChars( ));
		}
		// If it's 1, then only switch if it ranks higher than our current weapon.
		else if (( pCompareWeapon == NULL ) ||
			(( Owner->player->userinfo.switchonpickup == 1 ) && ( SelectionOrder < pCompareWeapon->SelectionOrder )))
		{
			Owner->player->PendingWeapon = this;

			// [BC] If we're a client, tell the server we're switching weapons.
			if (( NETWORK_GetState( ) == NETSTATE_CLIENT ) && (( Owner->player - players ) == consoleplayer ))
				CLIENTCOMMANDS_WeaponSelect( (char *)this->GetClass( )->TypeName.GetChars( ));
		}
		// [BC] The server doesn't have a status bar.
		if ( NETWORK_GetState( ) != NETSTATE_SERVER )
		{
			if (Owner->player->mo == players[consoleplayer].camera)
			{
				StatusBar->ReceivedWeapon (this);
			}
		}
	}
}

//===========================================================================
//
// AWeapon :: AddAmmo
//
// Give some ammo to the owner, even if it's just 0.
//
//===========================================================================

AAmmo *AWeapon::AddAmmo (AActor *other, const PClass *ammotype, int amount)
{
	AAmmo *ammo;

	if (ammotype == NULL)
	{
		return NULL;
	}
	// [BC] This behavior is from the original Doom. Give 5/2 times as much ammo when
	// we pick up a weapon in deathmatch.
	if (( deathmatch || teamgame ) && ( gameinfo.gametype == GAME_Doom ))
		amount *= 2.5;

	// extra ammo in baby mode and nightmare mode
	// [BC] Apply double ammo logic to weapon pickups as well.
	if ((gameskill == sk_baby || (gameskill == sk_nightmare && gameinfo.gametype != GAME_Strife)) ||
		( dmflags2 & DF2_YES_DOUBLEAMMO ))
	{
		if (gameinfo.gametype & (GAME_Doom|GAME_Strife))
			amount += amount;
		else
			amount += amount >> 1;
	}
	ammo = static_cast<AAmmo *>(other->FindInventory (ammotype));
	if (ammo == NULL)
	{
		ammo = static_cast<AAmmo *>(Spawn (ammotype, 0, 0, 0, NO_REPLACE));
		ammo->Amount = MIN (amount, ammo->MaxAmount);
		ammo->AttachToOwner (other);
	}
	else if (ammo->Amount < ammo->MaxAmount)
	{
		ammo->Amount += amount;
		if (ammo->Amount > ammo->MaxAmount)
		{
			ammo->Amount = ammo->MaxAmount;
		}
	}
	return ammo;
}

//===========================================================================
//
// AWeapon :: AddExistingAmmo
//
// Give the owner some more ammo he already has.
//
//===========================================================================

bool AWeapon::AddExistingAmmo (AAmmo *ammo, int amount)
{
	if (ammo != NULL && ammo->Amount < ammo->MaxAmount)
	{
		// extra ammo in baby mode and nightmare mode
		// [BC] Apply double ammo logic to weapon pickups as well.
		if ((gameskill == sk_baby || (gameskill == sk_nightmare && gameinfo.gametype != GAME_Strife)) ||
			( dmflags2 & DF2_YES_DOUBLEAMMO ))
		{
			if (gameinfo.gametype & (GAME_Doom|GAME_Strife))
				amount += amount;
			else
				amount += amount >> 1;
		}
		ammo->Amount += amount;
		if (ammo->Amount > ammo->MaxAmount)
		{
			ammo->Amount = ammo->MaxAmount;
		}
		return true;
	}
	return false;
}

//===========================================================================
//
// AWeapon :: AddWeapon
//
// Give the owner a weapon if they don't have it already.
//
//===========================================================================

AWeapon *AWeapon::AddWeapon (const PClass *weapontype)
{
	AWeapon *weap;

	if (weapontype == NULL)
	{
		return NULL;
	}
	weap = static_cast<AWeapon *>(Owner->FindInventory (weapontype));
	if (weap == NULL)
	{
		weap = static_cast<AWeapon *>(Spawn (weapontype, 0, 0, 0, NO_REPLACE));
		weap->AttachToOwner (Owner);
	}
	return weap;
}

//===========================================================================
//
// AWeapon :: ShouldStay
//
//===========================================================================

bool AWeapon::ShouldStay ()
{
	if (((( NETWORK_GetState( ) != NETSTATE_SINGLE ) &&
		(!deathmatch && !alwaysapplydmflags)) || (dmflags & DF_WEAPONS_STAY)) &&
		!(flags & MF_DROPPED))
	{
		return true;
	}
	return false;
}

//===========================================================================
//
// 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 altFire;
	int count1, count2;
	int enough, enoughmask;

	if (dmflags & DF_INFINITE_AMMO)
	{
		return true;
	}
	if (fireMode == EitherFire)
	{
		bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
		if (!gotSome && autoSwitch)
		{
			static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL);
		}
		return gotSome;
	}
	altFire = (fireMode == AltFire);
	if (!requireAmmo && (WeaponFlags & (WIF_AMMO_OPTIONAL << altFire)))
	{
		return true;
	}
	count1 = (Ammo1 != NULL) ? Ammo1->Amount : 0;
	count2 = (Ammo2 != NULL) ? Ammo2->Amount : 0;

	// [BC] If this is deathmatch, then use the ammo usage levels for DM instead.
/*
	if ( deathmatch || teamgame )
		enough = (count1 >= AmmoUseDM1) | ((count2 >= AmmoUseDM2) << 1);
	else
*/
		enough = (count1 >= AmmoUse1) | ((count2 >= AmmoUse2) << 1);
	if (WeaponFlags & (WIF_PRIMARY_USES_BOTH << altFire))
	{
		enoughmask = 3;
	}
	else
	{
		enoughmask = 1 << altFire;
	}
	if (altFire && AltAtkState == NULL)
	{ // If this weapon has no alternate fire, then there is never enough ammo for it
		enough &= 1;
	}
	if ((enough & enoughmask) == enoughmask)
	{
		return true;
	}
	// out of ammo, pick a weapon to change to
	if (autoSwitch)
	{
		static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL);
	}
	return false;
}

//===========================================================================
//
// AWeapon :: DepleteAmmo
//
// Use up some of the weapon's ammo. Returns true if the ammo was successfully
// depleted. If checkEnough is false, then the ammo will always be depleted,
// even if it drops below zero.
//
//===========================================================================

bool AWeapon::DepleteAmmo (bool altFire, bool checkEnough)
{
	if (!(dmflags & DF_INFINITE_AMMO))
	{
		if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false))
		{
			return false;
		}
		if (!altFire)
		{
			if (Ammo1 != NULL)
			{
				// [BC] If this is deathmatch, then use the ammo usage levels for DM instead.
/*
				if ( deathmatch || teamgame )
					Ammo1->Amount -= AmmoUseDM1;
				else
*/
					Ammo1->Amount -= AmmoUse1;
			}
			if ((WeaponFlags & WIF_PRIMARY_USES_BOTH) && Ammo2 != NULL)
			{
				// [BC] If this is deathmatch, then use the ammo usage levels for DM instead.
/*
				if ( deathmatch || teamgame )
					Ammo2->Amount -= AmmoUseDM2;
				else
*/
					Ammo2->Amount -= AmmoUse2;
			}
		}
		else
		{
			if (Ammo2 != NULL)
			{
				// [BC] If this is deathmatch, then use the ammo usage levels for DM instead.
/*
				if ( deathmatch || teamgame )
					Ammo2->Amount -= AmmoUseDM2;
				else
*/
					Ammo2->Amount -= AmmoUse2;
			}
			if ((WeaponFlags & WIF_ALT_USES_BOTH) && Ammo1 != NULL)
			{
				// [BC] If this is deathmatch, then use the ammo usage levels for DM instead.
/*
				if ( deathmatch || teamgame )
					Ammo1->Amount -= AmmoUseDM1;
				else
*/
					Ammo1->Amount -= AmmoUse1;
			}
		}
		if (Ammo1 != NULL && Ammo1->Amount < 0)
			Ammo1->Amount = 0;
		if (Ammo2 != NULL && Ammo2->Amount < 0)
			Ammo2->Amount = 0;
	}
	return true;
}


//===========================================================================
//
// AWeapon :: PostMorphWeapon
//
// Bring this weapon up after a player unmorphs.
//
//===========================================================================

void AWeapon::PostMorphWeapon ()
{
	Owner->player->PendingWeapon = WP_NOCHANGE;
	Owner->player->ReadyWeapon = this;
	Owner->player->psprites[ps_weapon].sy = WEAPONBOTTOM;
	P_SetPsprite (Owner->player, ps_weapon, UpState);
}

//===========================================================================
//
// AWeapon :: EndPowerUp
//
// The Tome of Power just expired.
//
//===========================================================================

void AWeapon::EndPowerup ()
{
	if (SisterWeapon != NULL && WeaponFlags&WIF_POWERED_UP)
	{
		if (ReadyState != SisterWeapon->ReadyState)
		{
			Owner->player->PendingWeapon = SisterWeapon;

			// [BC] If we're a client, tell the server we're switching weapons.
			if (( NETWORK_GetState( ) == NETSTATE_CLIENT ) && (( Owner->player - players ) == consoleplayer ))
				CLIENTCOMMANDS_WeaponSelect( ( char *)this->GetClass( )->TypeName.GetChars( ));
		}
		else
		{
			Owner->player->ReadyWeapon = SisterWeapon;

			// [BC] If we're a client, tell the server we're switching weapons.
			if (( NETWORK_GetState( ) == NETSTATE_CLIENT ) && (( Owner->player - players ) == consoleplayer ))
				CLIENTCOMMANDS_WeaponSelect( ( char *)SisterWeapon->GetClass( )->TypeName.GetChars( ));
		}
	}
}

//===========================================================================
//
// AWeapon :: GetUpState
//
//===========================================================================

FState *AWeapon::GetUpState ()
{
	return UpState;
}

//===========================================================================
//
// AWeapon :: GetDownState
//
//===========================================================================

FState *AWeapon::GetDownState ()
{
	return DownState;
}

//===========================================================================
//
// AWeapon :: GetReadyState
//
//===========================================================================

FState *AWeapon::GetReadyState ()
{
	return ReadyState;
}

//===========================================================================
//
// AWeapon :: GetAtkState
//
//===========================================================================

FState *AWeapon::GetAtkState ()
{
	return AtkState;
}

//===========================================================================
//
// AWeapon :: GetHoldAtkState
//
//===========================================================================

FState *AWeapon::GetHoldAtkState ()
{
	return HoldAtkState;
}

/* Weapon slots ***********************************************************/

FWeaponSlots LocalWeapons;

FWeaponSlot::FWeaponSlot ()
{
	Clear ();
}

void FWeaponSlot::Clear ()
{
	for (int i = 0; i < MAX_WEAPONS_PER_SLOT; i++)
	{
		Weapons[i] = NULL;
	}
}

bool FWeaponSlot::AddWeapon (const char *type)
{
	return AddWeapon (PClass::FindClass (type));
}

bool FWeaponSlot::AddWeapon (const PClass *type)
{
	int i;

	for (i = 0; i < MAX_WEAPONS_PER_SLOT; i++)
	{
		if (Weapons[i] == type)
			return true;	// Already present
		if (Weapons[i] == NULL)
			break;
	}
	if (i == MAX_WEAPONS_PER_SLOT)
	{ // This slot is full
		return false;
	}
	Weapons[i] = type;
	return true;
}

AWeapon *FWeaponSlot::PickWeapon (player_t *player)
{
	int i, j;

	if (player->ReadyWeapon != NULL)
	{
		for (i = 0; i < MAX_WEAPONS_PER_SLOT; i++)
		{
			if (Weapons[i] == player->ReadyWeapon->GetClass() ||
				(player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP &&
				 player->ReadyWeapon->SisterWeapon != NULL &&
				 player->ReadyWeapon->SisterWeapon->GetClass() == Weapons[i]))
			{
				for (j = (unsigned)(i - 1) % MAX_WEAPONS_PER_SLOT;
					j != i;
					j = (unsigned)(j - 1) % MAX_WEAPONS_PER_SLOT)
				{
					AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (Weapons[j]));

					// [BC] New cl_noammoswitch option: Allow users to switch to weapons even if they're out of ammo for them.
					if (weap != NULL && ( cl_noammoswitch || ( weap->CheckAmmo (AWeapon::EitherFire, false) )))
					{
						return weap;
					}
				}
			}
		}
	}
	for (i = MAX_WEAPONS_PER_SLOT - 1; i >= 0; i--)
	{
		AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (Weapons[i]));

		// [BC] New cl_noammoswitch option: Allow users to switch to weapons even if they're out of ammo for them.
		if (weap != NULL && ( cl_noammoswitch || ( weap->CheckAmmo (AWeapon::EitherFire, false) )))
		{
			return weap;
		}
	}
	return player->ReadyWeapon;
}

void FWeaponSlots::Clear ()
{
	for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
	{
		Slots[i].Clear ();
	}
}

// If the weapon already exists in a slot, don't add it. If it doesn't,
// then add it to the specified slot. False is returned if the weapon was
// not in a slot and could not be added. True is returned otherwise.

ESlotDef FWeaponSlots::AddDefaultWeapon (int slot, const PClass *type)
{
	int currSlot, index;

	if (!LocateWeapon (type, &currSlot, &index))
	{
		if (slot >= 0 && slot < NUM_WEAPON_SLOTS)
		{
			bool added = Slots[slot].AddWeapon (type);
			return added ? SLOTDEF_Added : SLOTDEF_Full;
		}
		return SLOTDEF_Full;
	}
	return SLOTDEF_Exists;
}

bool FWeaponSlots::LocateWeapon (const PClass *type, int *const slot, int *const index)
{
	int i, j;

	for (i = 0; i < NUM_WEAPON_SLOTS; i++)
	{
		for (j = 0; j < MAX_WEAPONS_PER_SLOT; j++)
		{
			if (Slots[i].Weapons[j] == type)
			{
				*slot = i;
				*index = j;
				return true;
			}
			else if (Slots[i].Weapons[j] == NULL)
			{ // No more weapons in this slot, so try the next
				break;
			}
		}
	}
	return false;
}

static bool FindMostRecentWeapon (player_s *player, int *slot, int *index)
{
	if (player->PendingWeapon != WP_NOCHANGE)
	{
		if (player->psprites[ps_weapon].state != NULL &&
			player->psprites[ps_weapon].state->GetAction() == A_Raise)
		{
			if (LocalWeapons.LocateWeapon (player->PendingWeapon->GetClass(), slot, index))
			{
				return true;
			}
			return false;
		}
		else
		{
			return LocalWeapons.LocateWeapon (player->PendingWeapon->GetClass(), slot, index);
		}
	}
	else if (player->ReadyWeapon != NULL)
	{
		AWeapon *weap = player->ReadyWeapon;
		if (!LocalWeapons.LocateWeapon (weap->GetClass(), slot, index))
		{
			if (weap->WeaponFlags & WIF_POWERED_UP && weap->SisterWeaponType != NULL)
			{
				return LocalWeapons.LocateWeapon (weap->SisterWeaponType, slot, index);
			}
			return false;
		}
		return true;
	}
	else
	{
		return false;
	}
}

AWeapon *PickNextWeapon (player_s *player)
{
	int startslot, startindex;

	if (player->ReadyWeapon == NULL || FindMostRecentWeapon (player, &startslot, &startindex))
	{
		int start;
		int i;

		if (player->ReadyWeapon == NULL)
		{
			startslot = NUM_WEAPON_SLOTS - 1;
			startindex = MAX_WEAPONS_PER_SLOT - 1;
		}
		start = startslot * MAX_WEAPONS_PER_SLOT + startindex;

		for (i = 1; i < NUM_WEAPON_SLOTS * MAX_WEAPONS_PER_SLOT + 1; i++)
		{
			int slot = (unsigned)((start + i) / MAX_WEAPONS_PER_SLOT) % NUM_WEAPON_SLOTS;
			int index = (unsigned)(start + i) % MAX_WEAPONS_PER_SLOT;
			const PClass *type = LocalWeapons.Slots[slot].Weapons[index];
			AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (type));

			// [BC] New cl_noammoswitch option: Allow users to switch to weapons even if they're out of ammo for them.
			if (weap != NULL && ( cl_noammoswitch || ( weap->CheckAmmo (AWeapon::EitherFire, false) )))
			{
				return weap;
			}
		}
	}
	return player->ReadyWeapon;
}

AWeapon *PickPrevWeapon (player_s *player)
{
	int startslot, startindex;

	if (player->ReadyWeapon == NULL || FindMostRecentWeapon (player, &startslot, &startindex))
	{
		int start;
		int i;

		if (player->ReadyWeapon == NULL)
		{
			startslot = 0;
			startindex = 0;
		}
		start = startslot * MAX_WEAPONS_PER_SLOT + startindex;

		for (i = 1; i < NUM_WEAPON_SLOTS * MAX_WEAPONS_PER_SLOT + 1; i++)
		{
			int slot = start - i;
			if (slot < 0)
				slot += NUM_WEAPON_SLOTS * MAX_WEAPONS_PER_SLOT;
			int index = slot % MAX_WEAPONS_PER_SLOT;
			slot /= MAX_WEAPONS_PER_SLOT;
			const PClass *type = LocalWeapons.Slots[slot].Weapons[index];
			AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (type));

			// [BC] New cl_noammoswitch option: Allow users to switch to weapons even if they're out of ammo for them.
			if (weap != NULL && ( cl_noammoswitch || ( weap->CheckAmmo (AWeapon::EitherFire, false) )))
			{
				return weap;
			}
		}
	}
	return player->ReadyWeapon;
}

CCMD (setslot)
{
	int slot, i;

	if (ParsingKeyConf && !WeaponSection)
	{
		Printf ("You need to use weaponsection before using setslot\n");
		return;
	}

	if (argv.argc() < 2 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
	{
		Printf ("Usage: setslot [slot] [weapons]\nCurrent slot assignments:\n");
		for (slot = 0; slot < NUM_WEAPON_SLOTS; ++slot)
		{
			Printf (" Slot %d:", slot);
			for (i = 0;
				i < MAX_WEAPONS_PER_SLOT && LocalWeapons.Slots[slot].GetWeapon(i) != NULL;
				++i)
			{
				Printf (" %s", LocalWeapons.Slots[slot].GetWeapon(i)->TypeName.GetChars());
			}
			Printf ("\n");
		}
		return;
	}

	LocalWeapons.Slots[slot].Clear();
	if (argv.argc() == 2)
	{
		Printf ("Slot %d cleared\n", slot);
	}
	else
	{
		for (i = 2; i < argv.argc(); ++i)
		{
			if (!LocalWeapons.Slots[slot].AddWeapon (argv[i]))
			{
				Printf ("Could not add %s to slot %d\n", argv[i], slot);
			}
		}
	}
}

CCMD (addslot)
{
	size_t slot;

	if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
	{
		Printf ("Usage: addslot <slot> <weapon>\n");
		return;
	}

	if (!LocalWeapons.Slots[slot].AddWeapon (argv[2]))
	{
		Printf ("Could not add %s to slot %d\n", argv[2], slot);
	}
}

CCMD (weaponsection)
{
	if (argv.argc() != 2)
	{
		Printf ("Usage: weaponsection <ini name>\n");
	}
	else
	{
		// Limit the section name to 32 chars
		if (strlen(argv[1]) > 32)
		{
			argv[1][32] = 0;
		}
		ReplaceString (&WeaponSection, argv[1]);

		// If the ini already has definitions for this section, load them
		char fullSection[32*3];
		char *tackOn;

		if (gameinfo.gametype == GAME_Hexen)
		{
			strcpy (fullSection, "Hexen");
			tackOn = fullSection + 5;
		}
		else if (gameinfo.gametype == GAME_Heretic)
		{
			strcpy (fullSection, "Heretic");
			tackOn = fullSection + 7;
		}
		else if (gameinfo.gametype == GAME_Strife)
		{
			strcpy (fullSection, "Strife");
			tackOn = fullSection + 6;
		}
		else
		{
			strcpy (fullSection, "Doom");
			tackOn = fullSection + 4;
		}

		sprintf (tackOn, ".%s.WeaponSlots", WeaponSection);
		if (GameConfig->SetSection (fullSection))
		{
			LocalWeapons.RestoreSlots (*GameConfig);
		}
	}
}

CCMD (addslotdefault)
{
	const PClass *type;
	unsigned int slot;

	if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
	{
		Printf ("Usage: addslotdefault <slot> <weapon>\n");
		return;
	}

	if (ParsingKeyConf && !WeaponSection)
	{
		Printf ("You need to use weaponsection before using addslotdefault\n");
		return;
	}

	type = PClass::FindClass (argv[2]);
	if (type == NULL || !type->IsDescendantOf (RUNTIME_CLASS(AWeapon)))
	{
		Printf ("%s is not a weapon\n", argv[2]);
	}

	switch (LocalWeapons.AddDefaultWeapon (slot, type))
	{
	case SLOTDEF_Full:
		Printf ("Could not add %s to slot %d\n", argv[2], slot);
		break;

	case SLOTDEF_Added:
		break;

	case SLOTDEF_Exists:
		break;
	}
}

int FWeaponSlots::RestoreSlots (FConfigFile &config)
{
	char buff[MAX_WEAPONS_PER_SLOT*64];
	const char *key, *value;
	int slot;
	int slotsread = 0;

	buff[sizeof(buff)-1] = 0;

	for (slot = 0; slot < NUM_WEAPON_SLOTS; ++slot)
	{
		Slots[slot].Clear ();
	}

	while (config.NextInSection (key, value))
	{
		if (strnicmp (key, "Slot[", 5) != 0 ||
			key[5] < '0' ||
			key[5] > '0'+NUM_WEAPON_SLOTS ||
			key[6] != ']' ||
			key[7] != 0)
		{
			continue;
		}
		slot = key[5] - '0';
		strncpy (buff, value, sizeof(buff)-1);
		char *tok;

		Slots[slot].Clear ();
		tok = strtok (buff, " ");
		while (tok != NULL)
		{
			Slots[slot].AddWeapon (tok);
			tok = strtok (NULL, " ");
		}
		slotsread++;
	}
	return slotsread;
}

void FWeaponSlots::SaveSlots (FConfigFile &config)
{
	char buff[MAX_WEAPONS_PER_SLOT*64];
	char keyname[16];

	for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
	{
		int index = 0;

		for (int j = 0; j < MAX_WEAPONS_PER_SLOT; ++j)
		{
			if (Slots[i].Weapons[j] == NULL)
			{
				break;
			}
			if (index > 0)
			{
				buff[index++] = ' ';
			}
			const char *name = Slots[i].Weapons[j]->TypeName.GetChars();
			strcpy (buff+index, name);
			index += (int)strlen (name);
		}
		if (index > 0)
		{
			sprintf (keyname, "Slot[%d]", i);
			config.SetValueForKey (keyname, buff);
		}
	}
}

int FWeaponSlot::CountWeapons ()
{
	int i;

	for (i = 0; i < MAX_WEAPONS_PER_SLOT; ++i)
	{
		if (Weapons[i] == NULL)
		{
			break;
		}
	}
	return i;
}
