/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/

#include "precompiled.h"
#pragma hdrstop

#pragma warning(disable : 4533 4800)



#include "WeaponItem.h"

#define WEAPON_START_AMMO_PREFIX "start_ammo_"
#define WEAPON_AMMO_REQUIRED "ammo_required"
#define WEAPON_MAX_AMMO "max_ammo"
#define WEAPON_IS_TOGGLEABLE "is_toggleable"

CInventoryWeaponItem::CInventoryWeaponItem() :
	CInventoryItem(NULL),
	m_WeaponDefName(""),
	m_MaxAmmo(0),
	m_Ammo(0),
	m_WeaponIndex(-1),
	m_AllowedEmpty(false),
	m_Enabled(true),
	m_WeaponIntent(EWeaponIntent::Unknown)
{
	SetType(IT_WEAPON);
}

CInventoryWeaponItem::CInventoryWeaponItem(const idStr& weaponDefName, idEntity* owner) :
	CInventoryItem(owner),
	m_WeaponDefName(weaponDefName),
	m_WeaponIndex(-1),
	m_AllowedEmpty(false),
	m_IsToggleable(false),
	m_Enabled(true),
	m_WeaponIntent(EWeaponIntent::Unknown)
{
	SetType(IT_WEAPON);

	const idDict* weaponDict = gameLocal.FindEntityDefDict(m_WeaponDefName);
	m_Name = weaponDict->GetString("inv_name", "Unknown weapon");

	m_WeaponName = weaponDict->GetString("inv_weapon_name");
	if (m_WeaponName.IsEmpty())
	{
		gameLocal.Warning("Weapon defined in %s has no 'inv_weapon_name' spawnarg!", m_WeaponDefName.c_str());
	}

	m_MaxAmmo = GetMaxAmmo();
	m_Ammo = GetStartAmmo();

	// Parse the common spawnargs which apply to both this and the base class
	ParseSpawnargs(*weaponDict);
}

void CInventoryWeaponItem::Save( idSaveGame *savefile ) const
{
	// Pass the call to the base class first
	CInventoryItem::Save(savefile);

	savefile->WriteString(m_WeaponDefName);
	savefile->WriteString(m_WeaponName);
	savefile->WriteString(m_ProjectileDefName);
	savefile->WriteInt(m_MaxAmmo);
	savefile->WriteInt(m_Ammo);
	savefile->WriteInt(m_WeaponIndex);
	savefile->WriteBool(m_AllowedEmpty);
	savefile->WriteBool(m_IsToggleable);
	savefile->WriteBool(m_Enabled);
}

void CInventoryWeaponItem::Restore( idRestoreGame *savefile )
{
	// Pass the call to the base class first
	CInventoryItem::Restore(savefile);

	savefile->ReadString(m_WeaponDefName);
	savefile->ReadString(m_WeaponName);
	savefile->ReadString(m_ProjectileDefName);
	savefile->ReadInt(m_MaxAmmo);
	savefile->ReadInt(m_Ammo);
	savefile->ReadInt(m_WeaponIndex);
	savefile->ReadBool(m_AllowedEmpty);
	savefile->ReadBool(m_IsToggleable);
	savefile->ReadBool(m_Enabled);
	m_WeaponIntent = EWeaponIntent::Unknown;
}

bool CInventoryWeaponItem::IsEnabled() const
{
	return m_Enabled;
}

void CInventoryWeaponItem::SetEnabled(bool enabled)
{
	m_Enabled = enabled;
}

int CInventoryWeaponItem::GetMaxAmmo()
{
	// Get the "max_ammo" spawnarg from the weapon dictionary
	const idDict* weaponDict = gameLocal.FindEntityDefDict(m_WeaponDefName,true); // grayman #3391 - don't create a default 'weaponDict'
																				// We want 'false' here, but FindEntityDefDict()
																				// will print its own warning, so let's not
																				// clutter the console with a redundant message
	if ( weaponDict == NULL )
	{
		return -1;
	}

	return weaponDict->GetInt(WEAPON_MAX_AMMO, "0");
}

bool CInventoryWeaponItem::IsAllowedEmpty() const
{
	return m_AllowedEmpty;
}

bool CInventoryWeaponItem::IsToggleable() const
{
	return m_IsToggleable;
}

int CInventoryWeaponItem::GetStartAmmo()
{
	// Sanity check
	if (m_Owner.GetEntity() == NULL) {
		return -1;
	}

	// Construct the weapon name to retrieve the "start_ammo_mossarrow" string, for instance
	idStr key = WEAPON_START_AMMO_PREFIX + m_WeaponName;
	return m_Owner.GetEntity()->spawnArgs.GetInt(key, "0");
}

int CInventoryWeaponItem::GetAmmo() const
{
	return m_Ammo;
}

void CInventoryWeaponItem::SetAmmo(int newAmount)
{
	if (!NeedsAmmo()) {
		// Don't set ammo of weapons that don't need any
		return;
	}

	m_Ammo = (newAmount > m_MaxAmmo) ? m_MaxAmmo : newAmount;

	if (m_Ammo < 0) {
		m_Ammo = 0;
	}
}

int CInventoryWeaponItem::HasAmmo()
{
	if (!NeedsAmmo()) {
		// Always return 1 for non-ammo weapons
		return 1;
	}
	return m_Ammo;
}

void CInventoryWeaponItem::UseAmmo(int amount)
{
	SetAmmo(m_Ammo - amount);
}

bool CInventoryWeaponItem::IsIntendedForCombat() const
{
	if (m_WeaponIntent == EWeaponIntent::Unknown)
	{
		if (idStr::Icmp(m_WeaponDefName, "atdm:weapon_ropearrow") == 0)
		{
			// Special case for rope arrow needed: It causes damage, but its primary intend is utility, not combat.
			m_WeaponIntent = EWeaponIntent::Utility;
			return false;
		}

		const idDict* weaponDict = gameLocal.FindEntityDefDict(m_WeaponDefName, true);
		if (weaponDict == nullptr)
			goto EarlyReturn;

		if (weaponDict->GetBool(WEAPON_AMMO_REQUIRED, "1"))
		{
			const char* projectileDefName = m_ProjectileDefName.IsEmpty() ?
				weaponDict->GetString("def_projectile") : m_ProjectileDefName.c_str();
			if (projectileDefName == nullptr)
				goto EarlyReturn;

			const idDict* projectileDict = gameLocal.FindEntityDefDict(projectileDefName, true);
			if (projectileDict == nullptr)
				goto EarlyReturn;
			
			const char* defDamage = projectileDict->GetString("def_damage");
			const bool doesNoDamage = idStr::Icmp(defDamage, "atdm:damage_none") == 0;
			m_WeaponIntent = doesNoDamage ? EWeaponIntent::Utility : EWeaponIntent::Combat;
		}
		else
		{
			// Melee
			const char* meleeDefName = weaponDict->GetString("def_melee");
			if (meleeDefName == nullptr)
				goto EarlyReturn;

			const idDict* meleeDict = gameLocal.FindEntityDefDict(meleeDefName, true);
			if (meleeDict == nullptr)
				goto EarlyReturn;

			static const int minDamage = 5; // Blackjack causes at least SOME damage
			const int damage = meleeDict->GetInt("damage", "0");
			m_WeaponIntent = damage >= minDamage ? EWeaponIntent::Combat : EWeaponIntent::Utility;
		}
	}

	return m_WeaponIntent == EWeaponIntent::Combat;

EarlyReturn:
	m_WeaponIntent = EWeaponIntent::Utility;
	return false;
}

void CInventoryWeaponItem::SetWeaponIndex(int index)
{
	m_WeaponIndex = index;

	// Now that the weapon index is known, cache a few values from the owner spawnargs

	const idDict* weaponDict = gameLocal.FindEntityDefDict(m_WeaponDefName,true); // grayman #3391 - don't create a default 'weaponDict'
																				// We want 'false' here, but FindEntityDefDict()
																				// will print its own warning, so let's not
																				// clutter the console with a redundant message
	if (weaponDict == NULL)
	{
		return;
	}

	m_AllowedEmpty = !weaponDict->GetBool(WEAPON_AMMO_REQUIRED, "1");
	m_IsToggleable = weaponDict->GetBool(WEAPON_IS_TOGGLEABLE, "0");

	// Initialise the projectile def name from the weapon spawnargs
	m_ProjectileDefName = weaponDict->GetString("def_projectile");
}

int CInventoryWeaponItem::GetWeaponIndex() const
{
	return m_WeaponIndex;
}

const idStr& CInventoryWeaponItem::GetWeaponName() const
{
	return m_WeaponName;
}

const idStr& CInventoryWeaponItem::GetProjectileDefName() const
{
	return m_ProjectileDefName;
}

void CInventoryWeaponItem::SetProjectileDefName(const idStr& weaponDefName)
{
	m_WeaponIntent  = EWeaponIntent::Unknown;
	m_ProjectileDefName = weaponDefName;
}

void CInventoryWeaponItem::ResetProjectileDefName()
{
	const idDict* weaponDict = gameLocal.FindEntityDefDict(m_WeaponDefName,true); // grayman #3391 - don't create a default 'projectileDef'
																				// We want 'false' here, but FindEntityDefDict()
																				// will print its own warning, so let's not
																				// clutter the console with a redundant message
	if (weaponDict == NULL)
	{
		return;
	}

	m_WeaponIntent  = EWeaponIntent::Unknown;
	m_ProjectileDefName = weaponDict->GetString("def_projectile");
}

const idStr& CInventoryWeaponItem::GetWeaponDefName() const
{
	return m_WeaponDefName;
}

void CInventoryWeaponItem::SaveItemEntityDict()
{
	// Don't call the base class, roll our own

	m_ItemDict.reset(new idDict);

	*m_ItemDict = *gameLocal.FindEntityDefDict(m_WeaponDefName);
}

void CInventoryWeaponItem::RestoreItemEntityFromDict(const idVec3& entPosition)
{
	// Don't call the base class, roll our own

	m_ItemDict.reset();
}
