/*
** armor.txt
** Implements all variations of armor objects
**
**---------------------------------------------------------------------------
** Copyright 2002-2016 Randy Heit
** Copyright 2006-2017 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

class Armor : Inventory
{
	Default
	{
		Inventory.PickupSound "misc/armor_pkup";
		+INVENTORY.ISARMOR
	}
}

//===========================================================================
//
//
// BasicArmor
//
// Basic armor absorbs a specific percent of the damage. You should
// never pickup a BasicArmor. Instead, you pickup a BasicArmorPickup
// or BasicArmorBonus and those gives you BasicArmor when it activates.
//
//
//===========================================================================

class BasicArmor : Armor
{
	
	int AbsorbCount;
	double SavePercent;
	int MaxAbsorb;
	int MaxFullAbsorb;
	int BonusCount;
	Name ArmorType;
	int ActualSaveAmount;
	
	Default
	{
		Inventory.Amount 0;
		+Inventory.KEEPDEPLETED
	}

	//===========================================================================
	//
	// ABasicArmor :: Tick
	//
	// If BasicArmor is given to the player by means other than a
	// BasicArmorPickup, then it may not have an icon set. Fix that here.
	//
	//===========================================================================

	override void Tick ()
	{
		Super.Tick ();
		AbsorbCount = 0;
		if (!Icon.isValid())
		{
			String icontex = gameinfo.ArmorIcon1;

			if (SavePercent >= gameinfo.Armor2Percent && gameinfo.ArmorIcon2.Length() != 0)
				icontex = gameinfo.ArmorIcon2;

			if (icontex.Length() != 0)
				Icon = TexMan.CheckForTexture (icontex, TexMan.TYPE_Any);
		}
	}

	//===========================================================================
	//
	// ABasicArmor :: CreateCopy
	//
	//===========================================================================

	override Inventory CreateCopy (Actor other)
	{
		// BasicArmor that is in use is stored in the inventory as BasicArmor.
		// BasicArmor that is in reserve is not.
		let copy = BasicArmor(Spawn("BasicArmor"));
		copy.SavePercent = SavePercent != 0 ? SavePercent : 0.33335;	// slightly more than 1/3 to avoid roundoff errors.
		copy.Amount = Amount;
		copy.MaxAmount = MaxAmount;
		copy.Icon = Icon;
		copy.BonusCount = BonusCount;
		copy.ArmorType = ArmorType;
		copy.ActualSaveAmount = ActualSaveAmount;
		GoAwayAndDie ();
		return copy;
	}


	//===========================================================================
	//
	// ABasicArmor :: HandlePickup
	//
	//===========================================================================

	override bool HandlePickup (Inventory item)
	{
		if (item.GetClass() == "BasicArmor")
		{
			// You shouldn't be picking up BasicArmor anyway.
			return true;
		}
		return false;
	}
	
	//===========================================================================
	//
	// ABasicArmor :: AbsorbDamage
	//
	//===========================================================================

	override void AbsorbDamage (int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags)
	{
		int saved;

		if (!DamageTypeDefinition.IgnoreArmor(damageType))
		{
			int full = MAX(0, MaxFullAbsorb - AbsorbCount);
			
			if (damage < full)
			{
				saved = damage;
			}
			else
			{
				saved = full + int((damage - full) * SavePercent);
				if (MaxAbsorb > 0 && saved + AbsorbCount > MaxAbsorb) 
				{
					saved = MAX(0,  MaxAbsorb - AbsorbCount);
				}
			}

			if (Amount < saved)
			{
				saved = Amount;
			}
			newdamage -= saved;
			Amount -= saved;
			AbsorbCount += saved;
			if (Amount == 0)
			{
				// The armor has become useless
				SavePercent = 0;
				ArmorType = 'None'; // Not NAME_BasicArmor.
				// Now see if the player has some more armor in their inventory
				// and use it if so. As in Strife, the best armor is used up first.
				BasicArmorPickup best = null;
				Inventory probe = Owner.Inv;
				while (probe != null)
				{
					let inInv = BasicArmorPickup(probe);
					if (inInv != null)
					{
						if (best == null || best.SavePercent < inInv.SavePercent)
						{
							best = inInv;
						}
					}
					probe = probe.Inv;
				}
				if (best != null)
				{
					Owner.UseInventory (best);
				}
			}
			damage = newdamage;
		}

		// Once the armor has absorbed its part of the damage, then apply its damage factor, if any, to the player
		if ((damage > 0) && (ArmorType != 'None')) // BasicArmor is not going to have any damage factor, so skip it.
		{
			newdamage = ApplyDamageFactors(ArmorType, damageType, damage, damage);
		}
	}
}

//===========================================================================
//
//
// BasicArmorBonus
//
//
//===========================================================================

class BasicArmorBonus : Armor 
{
	double SavePercent;	// The default, for when you don't already have armor
	int MaxSaveAmount;
	int MaxAbsorb;
	int MaxFullAbsorb;
	int SaveAmount;
	int BonusCount;
	int BonusMax;
	
	property prefix: Armor;
	property MaxSaveAmount: MaxSaveAmount;
	property SaveAmount : SaveAmount;
	property SavePercent: SavePercent;
	property MaxAbsorb: MaxAbsorb;
	property MaxFullAbsorb: MaxFullAbsorb;
	property MaxBonus: BonusCount;
	property MaxBonusMax: BonusMax;

	Default
	{
		+Inventory.AUTOACTIVATE
		+Inventory.ALWAYSPICKUP
		Inventory.MaxAmount 0;
		Armor.SavePercent 33.335;
	}
	
	//===========================================================================
	//
	// ABasicArmorBonus :: CreateCopy
	//
	//===========================================================================

	override Inventory CreateCopy (Actor other)
	{
		let copy = BasicArmorBonus(Super.CreateCopy (other));
		copy.SavePercent = SavePercent;
		copy.SaveAmount = SaveAmount;
		copy.MaxSaveAmount = MaxSaveAmount;
		copy.BonusCount = BonusCount;
		copy.BonusMax = BonusMax;
		copy.MaxAbsorb = MaxAbsorb;
		copy.MaxFullAbsorb = MaxFullAbsorb;

		return copy;
	}

	//===========================================================================
	//
	// ABasicArmorBonus :: Use
	//
	// Tries to add to the amount of BasicArmor a player has.
	//
	//===========================================================================

	override bool Use (bool pickup)
	{
		let armor = BasicArmor(Owner.FindInventory("BasicArmor"));
		bool result = false;

		// This should really never happen but let's be prepared for a broken inventory.
		if (armor == null)
		{
			armor = BasicArmor(Spawn("BasicArmor"));
			armor.BecomeItem ();
			armor.Amount = 0;
			armor.MaxAmount = MaxSaveAmount;
			Owner.AddInventory (armor);
		}

		if (BonusCount > 0 && armor.BonusCount < BonusMax)
		{
			armor.BonusCount = min(armor.BonusCount + BonusCount, BonusMax);
			result = true;
		}

		int saveAmount = min(GetSaveAmount(), MaxSaveAmount);

		if (saveAmount <= 0)
		{ // If it can't give you anything, it's as good as used.
			return BonusCount > 0 ? result : true;
		}

		// If you already have more armor than this item can give you, you can't
		// use it.
		if (armor.Amount >= MaxSaveAmount + armor.BonusCount)
		{
			return result;
		}

		if (armor.Amount <= 0)
		{ // Should never be less than 0, but might as well check anyway
			armor.Amount = 0;
			armor.Icon = Icon;
			armor.SavePercent = clamp(SavePercent, 0, 100) / 100;
			armor.MaxAbsorb = MaxAbsorb;
			armor.ArmorType = GetClassName();
			armor.MaxFullAbsorb = MaxFullAbsorb;
			armor.ActualSaveAmount = MaxSaveAmount;
		}

		armor.Amount = min(armor.Amount + saveAmount, MaxSaveAmount + armor.BonusCount);
		armor.MaxAmount = max(armor.MaxAmount, MaxSaveAmount);
		return true;
	}

	
	override void SetGiveAmount(Actor receiver, int amount, bool bycheat)
	{
		SaveAmount *= amount;
	}

	int GetSaveAmount ()
	{
		return !bIgnoreSkill ? int(SaveAmount * G_SkillPropertyFloat(SKILLP_ArmorFactor)) : SaveAmount;
	}
}

//===========================================================================
//
//
// BasicArmorPickup
//
//
//===========================================================================

class BasicArmorPickup : Armor
{

	double SavePercent;
	int MaxAbsorb;
	int MaxFullAbsorb;
	int SaveAmount;

	property prefix: Armor;
	property SaveAmount : SaveAmount;
	property SavePercent: SavePercent;
	property MaxAbsorb: MaxAbsorb;
	property MaxFullAbsorb: MaxFullAbsorb;

	Default
	{
		+Inventory.AUTOACTIVATE;
		Inventory.MaxAmount 0;
	}
	
	//===========================================================================
	//
	// ABasicArmorPickup :: CreateCopy
	//
	//===========================================================================

	override Inventory CreateCopy (Actor other)
	{
		let copy = BasicArmorPickup(Super.CreateCopy (other));
		copy.SavePercent = SavePercent;
		copy.SaveAmount = SaveAmount;
		copy.MaxAbsorb = MaxAbsorb;
		copy.MaxFullAbsorb = MaxFullAbsorb;

		return copy;
	}
	
	//===========================================================================
	//
	// ABasicArmorPickup :: Use
	//
	// Either gives you new armor or replaces the armor you already have (if
	// the SaveAmount is greater than the amount of armor you own). When the
	// item is auto-activated, it will only be activated if its max amount is 0
	// or if you have no armor active already.
	//
	//===========================================================================

	override bool Use (bool pickup)
	{
		int SaveAmount = GetSaveAmount();
		let armor = BasicArmor(Owner.FindInventory("BasicArmor"));

		// This should really never happen but let's be prepared for a broken inventory.
		if (armor == null)
		{
			armor = BasicArmor(Spawn("BasicArmor"));
			armor.BecomeItem ();
			Owner.AddInventory (armor);
		}
		else
		{
			// If you already have more armor than this item gives you, you can't
			// use it.
			if (armor.Amount >= SaveAmount + armor.BonusCount)
			{
				return false;
			}
			// Don't use it if you're picking it up and already have some.
			if (pickup && armor.Amount > 0 && MaxAmount > 0)
			{
				return false;
			}
		}
		
		armor.SavePercent = clamp(SavePercent, 0, 100) / 100;
		armor.Amount = SaveAmount + armor.BonusCount;
		armor.MaxAmount = SaveAmount;
		armor.Icon = Icon;
		armor.MaxAbsorb = MaxAbsorb;
		armor.MaxFullAbsorb = MaxFullAbsorb;
		armor.ArmorType = GetClassName();
		armor.ActualSaveAmount = SaveAmount;
		return true;
	}
	
	override void SetGiveAmount(Actor receiver, int amount, bool bycheat)
	{
		SaveAmount *= amount;
	}
	
	int GetSaveAmount ()
	{
		return !bIgnoreSkill ? int(SaveAmount * G_SkillPropertyFloat(SKILLP_ArmorFactor)) : SaveAmount;
	}
}

//===========================================================================
//
//
// HexenArmor
//
// Hexen armor consists of four separate armor types plus a conceptual armor
// type (the player himself) that work together as a single armor.
//
//
//===========================================================================

class HexenArmor : Armor
{
	
	double Slots[5];
	double SlotsIncrement[4];
	
	Default
	{
		+Inventory.KEEPDEPLETED
		+Inventory.UNTOSSABLE
	}
	
	//===========================================================================
	//
	// AHexenArmor :: CreateCopy
	//
	//===========================================================================

	override Inventory CreateCopy (Actor other)
	{
		// Like BasicArmor, HexenArmor is used in the inventory but not the map.
		// health is the slot this armor occupies.
		// Amount is the quantity to give (0 = normal max).
		let copy = HexenArmor(Spawn("HexenArmor"));
		copy.AddArmorToSlot (health, Amount);
		GoAwayAndDie ();
		return copy;
	}

	//===========================================================================
	//
	// AHexenArmor :: CreateTossable
	//
	// Since this isn't really a single item, you can't drop it. Ever.
	//
	//===========================================================================

	override Inventory CreateTossable (int amount)
	{
		return NULL;
	}

	//===========================================================================
	//
	// AHexenArmor :: HandlePickup
	//
	//===========================================================================

	override bool HandlePickup (Inventory item)
	{
		if (item is "HexenArmor")
		{
			if (AddArmorToSlot (item.health, item.Amount))
			{
				item.bPickupGood = true;
			}
			return true;
		}
		return false;
	}

	//===========================================================================
	//
	// AHexenArmor :: AddArmorToSlot
	//
	//===========================================================================

	protected bool AddArmorToSlot (int slot, int amount)
	{
		double hits;

		if (slot < 0 || slot > 3)
		{
			return false;
		}
		if (amount <= 0)
		{
			hits = SlotsIncrement[slot];
			if (Slots[slot] < hits)
			{
				Slots[slot] = hits;
				return true;
			}
		}
		else
		{
			hits = amount * 5;
			let total = Slots[0] + Slots[1] + Slots[2] + Slots[3] + Slots[4];
			let max = SlotsIncrement[0] + SlotsIncrement[1] + SlotsIncrement[2] + SlotsIncrement[3] + Slots[4] + 4 * 5;
			if (total < max)
			{
				Slots[slot] += hits;
				return true;
			}
		}
		return false;
	}

	//===========================================================================
	//
	// AHexenArmor :: AbsorbDamage
	//
	//===========================================================================

	override void AbsorbDamage (int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags)
	{
		if (!DamageTypeDefinition.IgnoreArmor(damageType))
		{
			double savedPercent = Slots[0] + Slots[1] + Slots[2] + Slots[3] + Slots[4];

			if (savedPercent)
			{ // armor absorbed some damage
				if (savedPercent > 100)
				{
					savedPercent = 100;
				}
				for (int i = 0; i < 4; i++)
				{
					if (Slots[i])
					{
						// 300 damage always wipes out the armor unless some was added
						// with the dragon skin bracers.
						if (damage < 10000)
						{
							Slots[i] -= damage * SlotsIncrement[i] / 300.;
							if (Slots[i] < 2)
							{
								Slots[i] = 0;
							}
						}
						else
						{
							Slots[i] = 0;
						}
					}
				}
				int saved = int(damage * savedPercent / 100.);
				if (saved > savedPercent*2)
				{	
					saved = int(savedPercent*2);
				}
				newdamage -= saved;
				damage = newdamage;
			}
		}
	}

	//===========================================================================
	//
	// AHexenArmor :: DepleteOrDestroy
	//
	//===========================================================================

	override void DepleteOrDestroy()
	{
		for (int i = 0; i < 4; i++)
		{
			Slots[i] = 0;
		}
	}	
}

