//=============================================================================
// InventoryManager
//	Base class to manage Pawn's inventory
//	This provides a simple interface to control and interact with the Pawn's inventory,
//	such as weapons, items and ammunition.
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//=============================================================================

class InventoryManager extends Actor
	native;


/** First inventory item in inventory linked list */
var Inventory InventoryChain;

/**
 * Player will switch to PendingWeapon, once the current weapon has been put down.
 * @fixme laurent -- PendingWeapon should be made protected, because too many bugs result by setting this variable directly.
 * It's only safe to read it, but to change it, SetCurrentWeapon() should be used.
 */
var			Weapon		PendingWeapon;

var			Weapon		LastAttemptedSwitchToWeapon;

/** if true, don't allow player to put down weapon without switching to another one */
var			bool		bMustHoldWeapon;

/** Holds the current "Fire" status for both firing modes */
var	private Array<INT>  PendingFire;

//
// Network replication.
//
replication
{

	if ( (!bSkipActorPropertyReplication || bNetInitial) && (Role==ROLE_Authority) && bNetDirty && bNetOwner )
		InventoryChain;
}

event PostBeginPlay()
{
	Super.PostBeginPlay();
	Instigator = Pawn(Owner);
}

simulated function INT GetPendingFireLength(Weapon InWeapon)
{
	return PendingFire.Length;
}

simulated function SetPendingFire(Weapon InWeapon, int InFiringMode)
{
	if( InFiringMode < PendingFire.Length )
	{
		PendingFire[InFiringMode] = 1;
	}
}

simulated function ClearPendingFire(Weapon InWeapon, int InFiringMode)
{
	if( InFiringMode < PendingFire.Length )
	{
		PendingFire[InFiringMode] = 0;
	}
}

simulated final function bool IsPendingFire(Weapon InWeapon, INT InFiringMode)
{
	return bool(PendingFire[InFiringMode]);
}

simulated function ClearAllPendingFire(Weapon InWeapon)
{
	local int i;
	for(i=0; i<PendingFire.length; i++)
	{
		PendingFire[i] = 0;
	}
}

/**
 * returns all Inventory Actors of class BaseClass
 *
 * @param	BaseClass	Inventory actors returned are of, or childs of, this base class.
 * @output	Inv			Inventory actors returned.
 * @note this iterator bails if it encounters more than 100 items, since temporary loops in linked list may sometimes be created
 *	on network clients while link pointers are being replicated. For performance reasons you shouldn't have that many inventory items anyway.
 */

native final iterator function InventoryActors( class<Inventory> BaseClass, out Inventory Inv );

/**
 * Setup Inventory for Pawn P.
 * Override this to change inventory assignment (from a pawn to another)
 * Network: Server only
 */
function SetupFor(Pawn P)
{
	Instigator = P;
	SetOwner(P);
}


/**	Event called when inventory manager is destroyed, called from Pawn.Destroyed() */
event Destroyed()
{
	DiscardInventory();
}


/**
 * Handle Pickup. Can Pawn pickup this item?
 *
 * @param	ItemClass Class of Inventory our Owner is trying to pick up
 * @param	Pickup the Actor containing that item (this may be a PickupFactory or it may be a DroppedPickup)
 *
 * @return	whether or not the Pickup actor should give its item to Other
 */
function bool HandlePickupQuery(class<Inventory> ItemClass, Actor Pickup)
{
	local Inventory	Inv;

	if( InventoryChain == None )
	{
		return TRUE;
	}

	// Give other Inventory Items a chance to deny this pickup
	ForEach InventoryActors(class'Inventory', Inv)
	{
		if( Inv.DenyPickupQuery(ItemClass, Pickup) )
		{
			return FALSE;
		}
	}
	return TRUE;
}


/**
 * returns the inventory item of the requested class if it exists in this inventory manager.
 * @param	DesiredClass	class of inventory item we're trying to find.
 * @param	bAllowSubclass whether subclasses of the desired class are acceptable
 * @return	Inventory actor if found, None otherwise.
 */
simulated event Inventory FindInventoryType(class<Inventory> DesiredClass, optional bool bAllowSubclass)
{
	local Inventory		Inv;

	ForEach InventoryActors(DesiredClass, Inv)
	{
		if (bAllowSubclass || Inv.Class == DesiredClass)
		{
			return Inv;
		}
	}
	return None;
}


/**
 * Spawns a new Inventory actor of NewInventoryItemClass type, and adds it to the Inventory Manager.
 * @param	NewInventoryItemClass		Class of inventory item to spawn and add.
 * @return	Inventory actor, None if couldn't be spawned.
 */
simulated function Inventory CreateInventory(class<Inventory> NewInventoryItemClass, optional bool bDoNotActivate)
{
	local Inventory	Inv;

	if( NewInventoryItemClass != None )
	{
		inv = Spawn(NewInventoryItemClass, Owner);
		if( inv != None )
		{
			if( !AddInventory(Inv, bDoNotActivate) )
			{
				`warn("InventoryManager::CreateInventory - Couldn't Add newly created inventory" @ Inv);
				Inv.Destroy();
				Inv = None;
			}
		}
		else
		{
			`warn("InventoryManager::CreateInventory - Couldn't spawn inventory" @ NewInventoryItemClass);
		}
	}

	return Inv;
}

/**
 * Adds an existing inventory item to the list.
 * Returns true to indicate it was added, false if it was already in the list.
 *
 * @param	NewItem		Item to add to inventory manager.
 * @return	true if item was added, false otherwise.
 */
simulated function bool AddInventory(Inventory NewItem, optional bool bDoNotActivate)
{
	local Inventory Item, LastItem;

	// The item should not have been destroyed if we get here.
	if( (NewItem != None) && !NewItem.bDeleteMe )
	{
		// if we don't have an inventory list, start here
		if( InventoryChain == None )
		{
			InventoryChain = newItem;
		}
		else
		{
			// Skip if already in the inventory.
			for (Item = InventoryChain; Item != None; Item = Item.Inventory)
			{
				if( Item == NewItem )
				{
					return FALSE;
				}
				LastItem = Item;
			}
			LastItem.Inventory = NewItem;
		}

		`LogInv("adding" @ NewItem @ "bDoNotActivate:" @ bDoNotActivate);

		NewItem.SetOwner( Instigator );
		NewItem.Instigator = Instigator;
		NewItem.InvManager = Self;
		NewItem.GivenTo( Instigator, bDoNotActivate);

		// Trigger inventory event
		Instigator.TriggerEventClass(class'SeqEvent_GetInventory', NewItem);
		return TRUE;
	}

	return FALSE;
}


/**
 * Attempts to remove an item from the inventory list if it exists.
 *
 * @param	Item	Item to remove from inventory
 */
simulated function RemoveFromInventory(Inventory ItemToRemove)
{
	local Inventory Item;
	local bool		bFound;

	if( ItemToRemove != None )
	{
		if( InventoryChain == ItemToRemove )
		{
			bFound = TRUE;
			InventoryChain = ItemToRemove.Inventory;
		}
		else
		{
			// If this item is in our inventory chain, unlink it.
			for(Item = InventoryChain; Item != None; Item = Item.Inventory)
			{
				if( Item.Inventory == ItemToRemove )
				{
					bFound = TRUE;
					Item.Inventory = ItemToRemove.Inventory;
					break;
				}
			}
		}

		if( bFound )
		{
			ItemToRemove.ItemRemovedFromInvManager();
			ItemToRemove.SetOwner(None);
			ItemToRemove.Inventory = None;
		}

		// make sure we don't have other references to the item
		if( ItemToRemove == Instigator.Weapon )
		{
			Instigator.Weapon = None;
		}

		if (Instigator.Health > 0 && Instigator.Weapon == None)
		{
			if (PendingWeapon != None && PendingWeapon != ItemToRemove)
			{
				`LogInv("Removed current weapon while changing weapons, call ChangedWeapon");
				ChangedWeapon();
			}
			else if(Instigator.Controller != None)
			{
				`LogInv("Calling ClientSwitchToBestWeapon to make sure a weapon is brought up");
				Instigator.Controller.ClientSwitchToBestWeapon(TRUE);
			}
		}
	}
}

/**
 * Discard full inventory, generally because the owner died
 */
simulated event DiscardInventory()
{
	local Inventory	Inv;
	local vector	TossVelocity;
	local bool		bBelowKillZ;

	`LogInv("");

	// don't drop any inventory if below KillZ or out of world
	bBelowKillZ = (Instigator == None) || (Instigator.Location.Z < WorldInfo.KillZ);

	ForEach InventoryActors(class'Inventory', Inv)
	{
		if( Inv.bDropOnDeath && !bBelowKillZ )
		{
			TossVelocity = vector(Instigator.GetViewRotation());
			TossVelocity = TossVelocity * ((Instigator.Velocity dot TossVelocity) + 500.f) + 250.f * VRand() + vect(0,0,250);
			Inv.DropFrom(Instigator.Location, TossVelocity);
		}
		else
		{
			Inv.Destroy();
		}
	}

	// Clear reference to Weapon
	Instigator.Weapon = None;

	// Clear reference to PendingWeapon
	PendingWeapon = None;
}

/** called when our owner is killed */
function OwnerDied()
{
	Destroy();
	if (Instigator.InvManager == self)
	{
		Instigator.InvManager = None;
	}
}

/**
 * Hook called from HUD actor. Gives access to HUD and Canvas
 *
 * @param	H	HUD
 */
simulated function DrawHud( HUD H );

/**
 * Returns a weight reflecting the desire to use the
 * given weapon, used for AI and player best weapon
 * selection.
 *
 * @param	Weapon W
 * @return	Weapon rating (range -1.f to 1.f)
 */
simulated function float GetWeaponRatingFor( Weapon W )
{
	local float Rating;

	if ( !W.HasAnyAmmo() )
		return -1;

	if (!Instigator.IsHumanControlled())
	{
		Rating = W.GetAIRating();
		// tend to stick with same weapon
		if (W == Instigator.Weapon && Instigator.Controller != None && Instigator.Controller.Enemy != None)
		{
			Rating += 0.21;
		}
	}
	else
	{
		Rating = 1;
	}

	return Rating;
}


/**
 * returns the best weapon for this Pawn in loadout
 */
simulated function Weapon GetBestWeapon( optional bool bForceADifferentWeapon, optional bool allow9mm )
{
	local Weapon	W, BestWeapon;
	local float		Rating, BestRating;

	ForEach InventoryActors( class'Weapon', W )
	{
		if( w.HasAnyAmmo() )
		{
			if( bForceADifferentWeapon &&
				W == Instigator.Weapon )
			{
				continue;
			}

			Rating = W.GetWeaponRating();
			if( BestWeapon == None ||
				Rating > BestRating )
			{
				BestWeapon = W;
				BestRating = Rating;
			}
		}
	}

	return BestWeapon;
}


/**
 * Switch to best weapon available in loadout
 * Network: LocalPlayer
 */
simulated function SwitchToBestWeapon( optional bool bForceADifferentWeapon, optional bool check_9mm_logic = false )
{
	local Weapon BestWeapon;

	`LogInv("bForceADifferentWeapon:" @ bForceADifferentWeapon);

	// if we don't already have a pending weapon,
	if( bForceADifferentWeapon ||
		PendingWeapon == None ||
		(AIController(Instigator.Controller) != None) )
	{
		// figure out the new weapon to bring up
		BestWeapon = GetBestWeapon( bForceADifferentWeapon );

		if( BestWeapon == None )
		{
			return;
		}

		// if it matches our current weapon then don't bother switching
		if( BestWeapon == Instigator.Weapon )
		{
			BestWeapon = None;
			PendingWeapon = None;
			Instigator.Weapon.Activate();
		}
	}

	// stop any current weapon fire
	Instigator.Controller.StopFiring();

	// and activate the new pending weapon
	SetCurrentWeapon(BestWeapon);
}


/**
 * Switches to Previous weapon
 * Network: Client
 */
simulated function PrevWeapon()
{
	local Weapon	CandidateWeapon, StartWeapon, W;

	StartWeapon = Instigator.Weapon;
	if ( PendingWeapon != None )
	{
		StartWeapon = PendingWeapon;
	}

	// Get previous
	ForEach InventoryActors( class'Weapon', W )
	{
		if ( W == StartWeapon )
		{
			break;
		}
		CandidateWeapon = W;
	}

	// if none found, get last
	if ( CandidateWeapon == None )
	{
		ForEach InventoryActors( class'Weapon', W )
		{
			CandidateWeapon = W;
		}
	}

	// If same weapon, do not change
	if ( CandidateWeapon == Instigator.Weapon )
	{
		return;
	}

	SetCurrentWeapon(CandidateWeapon);
}


/**
 * Switches to Next weapon
 * Network: Client
 */
simulated function NextWeapon()
{
	local Weapon	StartWeapon, CandidateWeapon, W;
	local bool		bBreakNext;

	StartWeapon = Instigator.Weapon;
	if( PendingWeapon != None )
	{
		StartWeapon = PendingWeapon;
	}

	ForEach InventoryActors( class'Weapon', W )
	{
		if( bBreakNext || (StartWeapon == None) )
		{
			CandidateWeapon = W;
			break;
		}
		if( W == StartWeapon )
		{
			bBreakNext = true;
		}
	}

	if( CandidateWeapon == None )
	{
		ForEach InventoryActors( class'Weapon', W )
		{
			CandidateWeapon = W;
			break;
		}
	}
	// If same weapon, do not change
	if( CandidateWeapon == Instigator.Weapon )
	{
		return;
	}

	SetCurrentWeapon(CandidateWeapon);
}


/**
 * Set DesiredWeapon as Current (Active) Weapon.
 * Network: LocalPlayer
 *
 * @param	DesiredWeapon, Desired weapon to assign to player
 */
reliable client function SetCurrentWeapon(Weapon DesiredWeapon)
{
	// Switch to this weapon
	InternalSetCurrentWeapon(DesiredWeapon);

	// Tell the server we have changed the pending weapon
	if( Role < Role_Authority )
	{
		ServerSetCurrentWeapon(DesiredWeapon);
	}
}

/**
 * ServerSetCurrentWeapon begins the Putdown sequence on the server.  This function makes
 * the assumption that if TryPutDown succeeded on the client, it will succeed on the server.
 * This function shouldn't be called from anywhere except SetCurrentWeapon
 *
 * Network: Dedicated Server
 */
reliable server function ServerSetCurrentWeapon(Weapon DesiredWeapon)
{
	InternalSetCurrentWeapon(DesiredWeapon);
}

simulated private function InternalSetCurrentWeapon(Weapon DesiredWeapon)
{
	local Weapon PrevWeapon;

	PrevWeapon = Instigator.Weapon;

	`LogInv("PrevWeapon:" @ PrevWeapon @ "DesiredWeapon:" @ DesiredWeapon);

	// Make sure we are switching to a new weapon
	// Handle the case where we're selecting again a weapon we've just deselected
	if( PrevWeapon != None && DesiredWeapon == PrevWeapon && !PrevWeapon.IsInState('WeaponPuttingDown') )
	{
		if(!DesiredWeapon.IsInState('Inactive') && !DesiredWeapon.IsInState('PendingClientWeaponSet'))
		{
			`LogInv("DesiredWeapon == PrevWeapon - abort"@DesiredWeapon.GetStateName());
			return;
		}
	}

	// Set the new weapon as pending
	SetPendingWeapon(DesiredWeapon);

	// if there is an old weapon handle it first.
	if( PrevWeapon != None && PrevWeapon != DesiredWeapon && !PrevWeapon.bDeleteMe && !PrevWeapon.IsInState('Inactive') )
	{
		// Try to put the weapon down.
		`LogInv("Try to put down previous weapon first.");
		PrevWeapon.TryPutdown();
	}
	else
	{
		// We don't have a weapon, force the call to ChangedWeapon
		ChangedWeapon();
	}
}


/**
 * Set the pending weapon for switching.
 * This shouldn't be called outside of SetCurrentWeapon()
 */
simulated function SetPendingWeapon(Weapon DesiredWeapon)
{
	`LogInv("SetPendingWeapon to" @ DesiredWeapon);
	// set the new weapon as pending
	PendingWeapon = DesiredWeapon;
}


/** Prevents player from being without a weapon. */
simulated function bool CancelWeaponChange()
{
	`LogInv(`showvar(PendingWeapon));
	// if PendingWeapon is None, prevent instigator from having no weapon,
	// so re-activate current weapon.
	if( PendingWeapon == None && bMustHoldWeapon )
	{
		PendingWeapon = Instigator.Weapon;
	}

	return FALSE;
}

/** Clear pending weapon, put it in a good state. */
simulated function ClearPendingWeapon()
{
	`LogInv(`showvar(PendingWeapon));

	if (PendingWeapon != None)
	{
		PendingWeapon.GotoState('Inactive');
		PendingWeapon = None;
	}
}


/**
 * ChangedWeapon is called when the current weapon is finished being deactivated
 */
simulated function ChangedWeapon()
{
	local Weapon OldWeapon;

	// Save current weapon as old weapon
	OldWeapon = Instigator.Weapon;

	// Make sure we can switch to a null weapon, otherwise, reactivate the current weapon
	`LogInv(`showvar(PendingWeapon)@`showvar(bMustHoldWeapon));
	if( PendingWeapon == None && bMustHoldWeapon )
	{
		if( OldWeapon != None )
		{
			OldWeapon.Activate();
			PendingWeapon = OldWeapon;
		}
	}

	`LogInv("switch from" @ OldWeapon @ "to" @ PendingWeapon);

	// switch to Pending Weapon
	Instigator.Weapon = PendingWeapon;

	// Play any Weapon Switch Animations
	Instigator.PlayWeaponSwitch(OldWeapon, PendingWeapon);

	// If we are going to an actual weapon, activate it.
	if( PendingWeapon != None )
	{
		// Setup the Weapon
		PendingWeapon.Instigator = Instigator;

		// Make some noise
		if( WorldInfo.Game != None )
		{
			Instigator.MakeNoise( 0.1, 'ChangedWeapon' );
		}

		// Activate the Weapon
		PendingWeapon.Activate();
		PendingWeapon = None;
	}

	// Notify of a weapon change
	if( Instigator.Controller != None )
	{
		Instigator.Controller.NotifyChangedWeapon(OldWeapon, Instigator.Weapon);
	}
}


/**
 * Weapon just given to a player, check if player should switch to this weapon
 * Network: LocalPlayer
 * Called from Weapon.ClientWeaponSet()
 */
simulated function ClientWeaponSet(Weapon NewWeapon, bool bOptionalSet, optional bool bDoNotActivate)
{
	local Weapon OldWeapon;

	`LogInv("NewWeapon:" @ NewWeapon @ "bOptionalSet:" @ bOptionalSet @ "bDoNotActivate:" @ bDoNotActivate);
	if( !bDoNotActivate )
	{
		OldWeapon = Instigator.Weapon;

		// If no current weapon, then set this one
		if( OldWeapon == None || OldWeapon.bDeleteMe || OldWeapon.IsInState('Inactive') )
		{
			`LogInv("OldWeapon == None or Inactive - Set new weapon right away" @ NewWeapon);
			SetCurrentWeapon(NewWeapon);
			return;
		}

		if( OldWeapon == NewWeapon )
		{
			if( NewWeapon.IsInState('PendingClientWeaponSet') )
			{
				`LogInv("OldWeapon == NewWeapon - but in PendingClientWeaponSet, so reset." @ NewWeapon);
				SetCurrentWeapon(NewWeapon);
			}
			else
			{
				`LogInv("OldWeapon == NewWeapon - abort" @ NewWeapon);
			}
			return;
		}

		if( bOptionalSet )
		{
			if( OldWeapon.DenyClientWeaponSet() ||
				(Instigator.IsHumanControlled() && PlayerController(Instigator.Controller).bNeverSwitchOnPickup) )
			{
				`LogInv("bOptionalSet && (DenyClientWeaponSet() || bNeverSwitchOnPickup) - abort" @ NewWeapon);

				LastAttemptedSwitchToWeapon = NewWeapon;
				return;
			}
		}

		if( PendingWeapon == None || !PendingWeapon.HasAnyAmmo() || PendingWeapon.GetWeaponRating() < NewWeapon.GetWeaponRating() )
		{
			// Compare switch priority and decide if we should switch to new weapon
			if( !Instigator.Weapon.HasAnyAmmo() || Instigator.Weapon.GetWeaponRating() < NewWeapon.GetWeaponRating() )
			{
				`LogInv("Switch to new weapon:" @ NewWeapon);
				SetCurrentWeapon(NewWeapon);
				return;
			}
		}
	}

	`LogInv("Send to inactive state" @ NewWeapon);
	NewWeapon.GotoState('Inactive');
}

simulated function UpdateController()
{
	local Inventory Item;
	local Weapon Weap;

	for (Item = InventoryChain; Item != None; Item = Item.Inventory)
	{
		Weap = Weapon(Item);
		if ( Weap != None )
		{
			Weap.CacheAIController();
		}
	}
}

defaultproperties
{
	TickGroup=TG_DuringAsyncWork

	bReplicateInstigator=TRUE
	RemoteRole=ROLE_SimulatedProxy
	bOnlyDirtyReplication=TRUE
	bOnlyRelevantToOwner=TRUE
	NetPriority=1.4
	bHidden=TRUE
	Physics=PHYS_None
	bReplicateMovement=FALSE
	bStatic=FALSE
	bNoDelete=FALSE
}
