//======== (C) Copyright 2002 Charles G. Cleveland All rights reserved. =========
//
// The copyright to the contents herein is the property of Charles G. Cleveland.
// The contents may be used and/or copied only with the written permission of
// Charles G. Cleveland, or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: 
//
// $Workfile: AvHPlayerUpgrade.cpp $
// $Date: 2002/11/05 06:17:26 $
//
//-------------------------------------------------------------------------------
// $Log: AvHPlayerUpgrade.cpp,v $
// Revision 1.25  2002/11/05 06:17:26  Flayra
// - Balance changes
//
// Revision 1.24  2002/10/20 02:36:14  Flayra
// - Regular update
//
// Revision 1.23  2002/10/18 22:22:04  Flayra
// - Toned down fade armor
//
// Revision 1.22  2002/10/04 18:03:43  Flayra
// - Soldier armor reduced back to 50 + 20 per upgrade (instead of 100 + 20).  Means two skulk bites to kill.
//
// Revision 1.21  2002/10/03 19:02:19  Flayra
// - Toned down primal scream
//
// Revision 1.20  2002/09/25 20:50:23  Flayra
// - Increased armor for soldiers to 100
// - Heavy armor now takes 100% of damage
//
// Revision 1.19  2002/09/23 22:02:56  Flayra
// - Fixed bug where damage upgrades were applying when they shouldn't (for aliens)
// - Added heavy armor
//
// Revision 1.18  2002/09/09 20:05:20  Flayra
// - More heavily armored cocooned aliens to allow them to morph in combat a bit more and for tension when encountered
//
// Revision 1.17  2002/08/31 18:01:02  Flayra
// - Work at VALVe
//
// Revision 1.16  2002/08/16 02:43:16  Flayra
// - Adjusted power armor slightly so it's not worse then regular armor at any point
//
// Revision 1.15  2002/07/28 19:21:28  Flayra
// - Balance changes after/during RC4a
//
// Revision 1.14  2002/07/23 17:18:47  Flayra
// - Level 1 armor toned down, power armor changes
//
// Revision 1.13  2002/07/01 21:43:56  Flayra
// - Added offensive upgrade back, when under the influence...of primal scream!
//
// Revision 1.12  2002/06/25 18:14:40  Flayra
// - Removed old offensive upgrades
//
// Revision 1.11  2002/06/03 16:55:49  Flayra
// - Toned down carapace and marine upgrades
//
// Revision 1.10  2002/05/23 02:33:20  Flayra
// - Post-crash checkin.  Restored @Backup from around 4/16.  Contains changes for last four weeks of development.
//
//===============================================================================
#include "mod/AvHPlayerUpgrade.h"
#include "mod/AvHSpecials.h"
#include "mod/AvHBalance.h"
#include "common/damagetypes.h"

const int	kWeaponTracerDefault		= 0;
const int	kWeaponTracerLevelOne		= 6;
const int	kWeaponTracerLevelTwo		= 4;
const int	kWeaponTracerLevelThree		= 2;

float AvHPlayerUpgrade::GetAlienMeleeDamageUpgrade(int inUpgrade, bool inIncludeFocus)
{
	float theMultiplier = 1.0f;
	
	if(GetHasUpgrade(inUpgrade, MASK_BUFFED))
	{
		theMultiplier = 1.0f + (float)BALANCE_FVAR(kPrimalScreamDamageModifier);
	}

    if(inIncludeFocus)
    {
        theMultiplier *= AvHPlayerUpgrade::GetFocusDamageUpgrade(inUpgrade);
    }
	
	return theMultiplier;
}

float AvHPlayerUpgrade::GetAlienRangedDamageUpgrade(int inUpgrade)
{
	float theMultiplier = 1.0f;
	
	if(GetHasUpgrade(inUpgrade, MASK_BUFFED))
	{
		theMultiplier = 1.0f + (float)BALANCE_FVAR(kPrimalScreamDamageModifier);
	}

	return theMultiplier;
}

float AvHPlayerUpgrade::GetFocusDamageUpgrade(int inUpgrade)
{
	float theFocusDamageUpgrade = 1.0f;

	int theFocusLevel = AvHGetAlienUpgradeLevel(inUpgrade, MASK_UPGRADE_8);
	if(theFocusLevel > 0)
	{
		// Increase damage for each level of focus
		const float theFocusDamageUpgradePercentPerLevel = (float)BALANCE_FVAR(kFocusDamageUpgradePercentPerLevel);
		theFocusDamageUpgrade += theFocusLevel*theFocusDamageUpgradePercentPerLevel;
	}

	return theFocusDamageUpgrade;
}

float AvHPlayerUpgrade::GetArmorValue(int inNumHives)
{
	// Each point of armor is work 1/x points of health
	float theArmorValueNonAlien = BALANCE_FVAR(kArmorValueNonAlien);
	float theArmorBonus = theArmorValueNonAlien;

	if(inNumHives > 0)
	{
		float theArmorValueBase = 1.0f + (float)BALANCE_FVAR(kArmorValueBase);
		float theArmorValuePerHive = (float)BALANCE_FVAR(kArmorValuePerHive);
		inNumHives = min(inNumHives, kMaxHives);

		theArmorBonus = (theArmorValueBase + inNumHives*theArmorValuePerHive);
	}

	// Smaller is better
	theArmorBonus = 1.0f/theArmorBonus;

	return theArmorBonus;
}

float AvHPlayerUpgrade::GetArmorAbsorption(AvHUser3 inUser3, int inUpgrade, int inNumHives)
{
	// A value of .2 means the armor Takes 80% of the damage, so the value gets smaller as it improves
	float theAbsorption = (float)BALANCE_FVAR(kArmorAbsorptionBase);
	inNumHives = min(inNumHives, kMaxHives);//voogru: prevent aliens taking negative damage if some mapper goon (or me :o) ) decides to put more than 3 hives on the map.

	// Heavy armor is the shiznit
	if(inUser3 == AVH_USER3_MARINE_PLAYER && GetHasUpgrade(inUpgrade, MASK_UPGRADE_13))
	{
		float theHeavyArmorAbsorbPercent = BALANCE_IVAR(kHeavyArmorAbsorbPercent)/100.0f;
		ASSERT(theHeavyArmorAbsorbPercent >= 0.0f);
		ASSERT(theHeavyArmorAbsorbPercent <= 1.0f);

		theAbsorption = 1.0f - theHeavyArmorAbsorbPercent;
	}

    // Increase absorption at higher hive-levels to make sure armor is always used before player dies
    if(inNumHives)
    {
        switch(inUser3)
        {
        case AVH_USER3_ALIEN_PLAYER1:
        case AVH_USER3_ALIEN_PLAYER2:
        case AVH_USER3_ALIEN_PLAYER3:
        case AVH_USER3_ALIEN_PLAYER4:
        case AVH_USER3_ALIEN_PLAYER5:
        case AVH_USER3_ALIEN_EMBRYO:
            theAbsorption -= (inNumHives - 1)*(float)BALANCE_FVAR(kArmorAbsorptionPerExtraHive);
            break;
        }
    }

	ASSERT(theAbsorption >= 0.0f);
	ASSERT(theAbsorption <= 1.0f);

	return theAbsorption;
}

int AvHPlayerUpgrade::GetMaxHealth(int inUpgrade, AvHUser3 inUser3, int inLevel)
{
	int theMaxHealth = 0;
	int theHealthLevelIncrementPercent = BALANCE_IVAR(kCombatLevelHealthIncrease);

	// TODO: Take into account upgrade if added
	
	switch(inUser3)
	{
	default:
	case AVH_USER3_MARINE_PLAYER:
	case AVH_USER3_COMMANDER_PLAYER:
		theMaxHealth = BALANCE_IVAR(kMarineHealth);
		break;
		
	case AVH_USER3_ALIEN_PLAYER1:
		theMaxHealth = BALANCE_IVAR(kSkulkHealth);
		break;
		
	case AVH_USER3_ALIEN_PLAYER2:
		theMaxHealth = BALANCE_IVAR(kGorgeHealth);
		break;
		
	case AVH_USER3_ALIEN_PLAYER3:
		theMaxHealth = BALANCE_IVAR(kLerkHealth);
		break;
		
	case AVH_USER3_ALIEN_PLAYER4:
		theMaxHealth = BALANCE_IVAR(kFadeHealth);
		break;
		
	case AVH_USER3_ALIEN_PLAYER5:
		theMaxHealth = BALANCE_IVAR(kOnosHealth);
		break;

	case AVH_USER3_ALIEN_EMBRYO:
		theMaxHealth = BALANCE_IVAR(kGestateHealth);
		break;
	}
	
	//return (1.0f + (inLevel - 1)*theHealthLevelIncrementPercent)*theMaxHealth;
	return theMaxHealth + (inLevel - 1)*theHealthLevelIncrementPercent;
}


int AvHPlayerUpgrade::GetMaxArmorLevel(int inUpgrade, AvHUser3 inUser3)
{
	int theMaxArmorLevel = 0;

	int theArmorLevel = AvHPlayerUpgrade::GetArmorUpgrade(inUser3, inUpgrade);
	//bool theHasAlienCarapace = GetHasUpgrade(inUpgrade, MASK_UPGRADE_1);
	bool theHasPowerArmor = GetHasUpgrade(inUpgrade, MASK_UPGRADE_10);
	bool theHasHeavyArmor = GetHasUpgrade(inUpgrade, MASK_UPGRADE_13);

	// Upgrade TODO: Take different levels into account?

	switch(inUser3)
	{
	default:
	case AVH_USER3_MARINE_PLAYER:
	case AVH_USER3_COMMANDER_PLAYER:
		//theMaxArmorLevel = 100 + theArmorLevel*20;
		theMaxArmorLevel = BALANCE_IVAR(kMarineBaseArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kMarineArmorUpgrade));
		if(theHasHeavyArmor)
		{
			theMaxArmorLevel = BALANCE_IVAR(kMarineBaseHeavyArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kMarineHeavyArmorUpgrade));
		}
		//if(theHasPowerArmor)
		//{
		//	theMaxArmorLevel *= 2;
		//}
		break;

	case AVH_USER3_ALIEN_PLAYER1:
		theMaxArmorLevel = BALANCE_IVAR(kSkulkBaseArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kSkulkArmorUpgrade));//(theHasAlienCarapace ? 30 : 10);
		break;

	case AVH_USER3_ALIEN_PLAYER2:
		theMaxArmorLevel = BALANCE_IVAR(kGorgeBaseArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kGorgeArmorUpgrade));//(theHasAlienCarapace ? 75 : 50);
		break;

	case AVH_USER3_ALIEN_PLAYER3:
		theMaxArmorLevel = BALANCE_IVAR(kLerkBaseArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kLerkArmorUpgrade));//(theHasAlienCarapace ? 75 : 50);
		break;																								

	case AVH_USER3_ALIEN_PLAYER4:
		theMaxArmorLevel = BALANCE_IVAR(kFadeBaseArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kFadeArmorUpgrade));//(theHasAlienCarapace ? 150 : 125);
		break;

	case AVH_USER3_ALIEN_PLAYER5:
		theMaxArmorLevel = BALANCE_IVAR(kOnosBaseArmor) + (int)((theArmorLevel/3.0f)*BALANCE_IVAR(kOnosArmorUpgrade));//(theHasAlienCarapace ? 200 : 150);
		break;

	case AVH_USER3_ALIEN_EMBRYO:
		theMaxArmorLevel = BALANCE_IVAR(kGestateBaseArmor);
		break;

	}

	return theMaxArmorLevel;
}

// Returns the level: 0, 1, 2 or 3
int AvHPlayerUpgrade::GetArmorUpgrade(AvHUser3 inUser3, int inUpgrade, float* theArmorMultiplier)
{
	int theUpgradeLevel = 0;

	if(theArmorMultiplier)
		*theArmorMultiplier = 1.0f;

	bool theIsMarine = false;
	switch(inUser3)
	{
	case AVH_USER3_MARINE_PLAYER:
	case AVH_USER3_COMMANDER_PLAYER:
		theIsMarine = true;
		break;
	}

	if(theIsMarine)
	{
		if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_6))
		{
			if(theArmorMultiplier)
				*theArmorMultiplier = 1.0f + (float)BALANCE_FVAR(kMarineArmorLevelThree);
			theUpgradeLevel = 3;
		}
		else if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_5))
		{
			if(theArmorMultiplier)
				*theArmorMultiplier = 1.0f + (float)BALANCE_FVAR(kMarineArmorLevelTwo);
			
			theUpgradeLevel = 2;
		}
		else if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_4))
		{
			if(theArmorMultiplier)
				*theArmorMultiplier = 1.0f + (float)BALANCE_FVAR(kMarineArmorLevelOne);
			
			theUpgradeLevel = 1;
		}
    }
	else
	{
		if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_1))
		{
			if(theArmorMultiplier)
			{
				*theArmorMultiplier = 1.0f + (float)BALANCE_FVAR(kAlienArmorLevelOne);
			}
			theUpgradeLevel = 1;

			if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_10))
			{
				if(theArmorMultiplier)
				{
					*theArmorMultiplier = 1.0f + (float)BALANCE_FVAR(kAlienArmorLevelTwo);
				}
				
				theUpgradeLevel = 2;
			}
			if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_11))
			{
				if(theArmorMultiplier)
				{
					*theArmorMultiplier = 1.0f + (float)BALANCE_FVAR(kAlienArmorLevelThree);
				}
				
				theUpgradeLevel = 3;
			}
		}
	}
	
	return theUpgradeLevel;
}

// Returns the level: 0, 1, 2 or 3
int AvHPlayerUpgrade::GetWeaponUpgrade(int inUser3, int inUpgrade, float* outDamageMultiplier, int* outTracerFreq, float* outSpread)
{
	int theUpgradeLevel = 0;
	
	if(outDamageMultiplier)
		*outDamageMultiplier = 1.0f;

	if(outTracerFreq)
		*outTracerFreq = kWeaponTracerDefault;

	// Only marines and marine items can get damage upgrades
	switch(inUser3)
	{
	case AVH_USER3_MARINE_PLAYER:
	case AVH_USER3_TURRET:
	case AVH_USER3_SIEGETURRET:
	case AVH_USER3_MARINEITEM:
    case AVH_USER3_MINE:
	case AVH_USER3_NUKE:
		// Apply extra damage for upgrade
		if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_3))
		{
			if(outDamageMultiplier)
				*outDamageMultiplier *= (1.0f + (float)BALANCE_FVAR(kWeaponDamageLevelThree));
			if(outTracerFreq)
				*outTracerFreq = kWeaponTracerLevelThree;
		
			theUpgradeLevel = 3;
		} 
		else if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_2))
		{
			if(outDamageMultiplier)
				*outDamageMultiplier *= (1.0f + (float)BALANCE_FVAR(kWeaponDamageLevelTwo));
			if(outTracerFreq)
				*outTracerFreq = kWeaponTracerLevelTwo;
			theUpgradeLevel = 2;
		}
		else if(GetHasUpgrade(inUpgrade, MASK_UPGRADE_1))
		{
			if(outDamageMultiplier)
				*outDamageMultiplier *= (1.0f + (float)BALANCE_FVAR(kWeaponDamageLevelOne));
		
			if(outTracerFreq)
				*outTracerFreq = kWeaponTracerLevelOne;
		
			theUpgradeLevel = 1;
		}
		break;
	}
	
	return theUpgradeLevel;
}

float AvHPlayerUpgrade::GetSilenceVolumeLevel(AvHUser3 inUser3, int inUpgrade)
{
	int theSilenceLevel = 0;

	switch(inUser3)
	{
	case AVH_USER3_ALIEN_PLAYER1:
	case AVH_USER3_ALIEN_PLAYER2:
	case AVH_USER3_ALIEN_PLAYER3:
	case AVH_USER3_ALIEN_PLAYER4:
	case AVH_USER3_ALIEN_PLAYER5:
	case AVH_USER3_ALIEN_EMBRYO:
		theSilenceLevel = AvHGetAlienUpgradeLevel(inUpgrade, MASK_UPGRADE_6);
		break;
	}
	
	//float theSilenceVolumeFactor = (1 - (theSilenceLevel/3.0f));

	float theSilenceVolumeFactor = 1.0f;
	switch(theSilenceLevel)
	{
	case 1:
		theSilenceVolumeFactor = (float)BALANCE_FVAR(kSilenceLevel1Volume);
		break;
	case 2:
		theSilenceVolumeFactor = (float)BALANCE_FVAR(kSilenceLevel2Volume);
		break;
	case 3:
		theSilenceVolumeFactor = (float)BALANCE_FVAR(kSilenceLevel3Volume);
		break;
	}

	return theSilenceVolumeFactor;
}

float AvHPlayerUpgrade::CalculateDamageLessArmor(AvHUser3 inUser3, int inUser4, float flDamage, float& ioArmorValue, int bitsDamageType, int inNumHives)
{
	// if we're alien, if we have the armor upgrade, we take less damage off the top
//	if(AvHGetIsAlien(inUser3))
//	{
//		int theArmorUpgradeLevel = AvHGetAlienUpgradeLevel(inUser4, MASK_UPGRADE_1);
//		if((theArmorUpgradeLevel > 0) && ((int)(ioArmorValue) > 0))
//		{
//			float thePercentageOffTop = .1f*theArmorUpgradeLevel;
//			flDamage -= flDamage*thePercentageOffTop; 
//		}
//	}
	
	float flRatio = AvHPlayerUpgrade::GetArmorAbsorption(inUser3, inUser4, inNumHives);
	float flBonus = AvHPlayerUpgrade::GetArmorValue(inNumHives);
	
	// Level 1 aliens don't take falling damage, ever
	if((inUser3 == AVH_USER3_ALIEN_PLAYER1) && (bitsDamageType & DMG_FALL))
	{
		flDamage = 0.0f;
	}
	
	// Armor. 
	if (ioArmorValue && !(bitsDamageType & (DMG_FALL | DMG_DROWN)) )// armor doesn't protect against fall or drown damage!
	{
		float flNew = flDamage*flRatio;
		float flArmor = (flDamage - flNew)*flBonus;
		
		// Does this use more armor than we have?
		if (flArmor > ioArmorValue)
		{
			flArmor = ioArmorValue;
			flArmor *= (1/flBonus);
			flNew = flDamage - flArmor;
			ioArmorValue = 0;
		}
		else
		{
			ioArmorValue -= flArmor;
			if ( bitsDamageType & (NS_DMG_ACID) )
			{
				ioArmorValue -= flArmor;
			}
		}
	
		flDamage = flNew;
	}
	
	return flDamage;
}

const int kCombatMinLevel = 1;
const int kCombatMaxLevel = 10;

float AvHPlayerUpgrade::GetExperienceForLevel(int inLevel)
{
	float theExperienceForLevel = 0.0f;
	int theLevel = 1;

    int theCombatBaseExperience = BALANCE_IVAR(kCombatBaseExperience);
	float theCombatLevelExperienceModifier = (float)BALANCE_FVAR(kCombatLevelExperienceModifier);

	while((theLevel < inLevel) && (theCombatLevelExperienceModifier > 0))
	{
		theExperienceForLevel += (1.0f + (theLevel-1)*theCombatLevelExperienceModifier)*theCombatBaseExperience;
		theLevel++;
	}
	
	return theExperienceForLevel;
}

int AvHPlayerUpgrade::GetPlayerLevel(float inExperience)
{
	int thePlayerLevel = 1;

    int theCombatBaseExperience = BALANCE_IVAR(kCombatBaseExperience);
	float theCombatLevelExperienceModifier = (float)BALANCE_FVAR(kCombatLevelExperienceModifier);

	while((inExperience > 0) && (theCombatLevelExperienceModifier > 0))
	{
		inExperience -= (1.0f + (thePlayerLevel - 1)*theCombatLevelExperienceModifier)*theCombatBaseExperience;

		if(inExperience > 0)
		{
			thePlayerLevel++;
		}
	}

	thePlayerLevel = max(min(thePlayerLevel, kCombatMaxLevel), kCombatMinLevel);

	return thePlayerLevel;
}

float AvHPlayerUpgrade::GetPercentToNextLevel(float inExperience)
{
	int theCurrentLevel = AvHPlayerUpgrade::GetPlayerLevel(inExperience);
	int theNextLevel = min(max(theCurrentLevel + 1, kCombatMinLevel), kCombatMaxLevel);

	float theExperienceForCurrentLevel = AvHPlayerUpgrade::GetExperienceForLevel(theCurrentLevel);
	float theExperienceForNextLevel = AvHPlayerUpgrade::GetExperienceForLevel(theNextLevel);

	float thePercent = (inExperience - theExperienceForCurrentLevel)/(theExperienceForNextLevel - theExperienceForCurrentLevel);

	thePercent = min(max(0.0f, thePercent), 1.0f);

	return thePercent;
}