/*
* This file is part of aion-unique <aion-unique.smfnew.com>.
*
* aion-unique 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.
*
* aion-unique is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with aion-unique. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.gameserver.utils.stats;
import org.apache.log4j.Logger;
import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.controllers.attack.AttackStatus;
import com.aionemu.gameserver.model.SkillElement;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.Summon;
import com.aionemu.gameserver.model.gameobjects.player.Equipment;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.state.CreatureState;
import com.aionemu.gameserver.model.gameobjects.stats.CreatureGameStats;
import com.aionemu.gameserver.model.gameobjects.stats.StatEnum;
import com.aionemu.gameserver.model.templates.item.WeaponType;
import com.aionemu.gameserver.model.templates.stats.NpcRank;
/**
* @author ATracer
* @author alexa026
*/
public class StatFunctions
{
private static Logger log = Logger.getLogger(StatFunctions.class);
/**
*
* @param player
* @param target
* @return XP reward from target
*/
public static long calculateSoloExperienceReward(Player player, Creature target)
{
int playerLevel = player.getCommonData().getLevel();
int targetLevel = target.getLevel();
int baseXP = ((Npc)target).getObjectTemplate().getStatsTemplate().getMaxXp();
int xpPercentage = XPRewardEnum.xpRewardFrom(targetLevel - playerLevel);
return (int) Math.floor(baseXP * xpPercentage * player.getRates().getXpRate() / 100);
}
/**
*
* @param player
* @param target
* @return
*/
public static long calculateGroupExperienceReward(Player player, Creature target)
{
int playerLevel = player.getCommonData().getLevel();
int targetLevel = target.getLevel();
int baseXP = ((Npc)target).getObjectTemplate().getStatsTemplate().getMaxXp();
int xpPercentage = XPRewardEnum.xpRewardFrom(targetLevel - playerLevel);
return (int) Math.floor(baseXP * xpPercentage / 100);
}
/**
* ref: http://www.aionsource.com/forum/mechanic-analysis/42597-character-stats-xp-dp-origin-gerbator-team-july-2009-a.html
*
* @param player
* @param target
* @return DP reward from target
*/
public static int calculateSoloDPReward(Player player, Creature target)
{
int playerLevel = player.getCommonData().getLevel();
int targetLevel = target.getLevel();
NpcRank npcRank = ((Npc) target).getObjectTemplate().getRank();
//TODO: fix to see monster Rank level, NORMAL lvl 1, 2 | ELITE lvl 1, 2 etc..
//look at: http://www.aionsource.com/forum/mechanic-analysis/42597-character-stats-xp-dp-origin-gerbator-team-july-2009-a.html
int baseDP = targetLevel * calculateRankMultipler(npcRank);
int xpPercentage = XPRewardEnum.xpRewardFrom(targetLevel - playerLevel);
return (int) Math.floor(baseDP * xpPercentage * player.getRates().getXpRate() / 100);
}
/**
*
* @param player
* @param target
* @return AP reward
*/
public static int calculateSoloAPReward(Player player, Creature target)
{
int playerLevel = player.getCommonData().getLevel();
int targetLevel = target.getLevel();
int percentage = XPRewardEnum.xpRewardFrom(targetLevel - playerLevel);
return (int) Math.floor(10 * percentage * player.getRates().getApNpcRate() / 100);
}
/**
*
* @param player
* @param target
* @return DP reward
*/
public static int calculateGroupDPReward(Player player, Creature target)
{
int playerLevel = player.getCommonData().getLevel();
int targetLevel = target.getLevel();
NpcRank npcRank = ((Npc) target).getObjectTemplate().getRank();
//TODO: fix to see monster Rank level, NORMAL lvl 1, 2 | ELITE lvl 1, 2 etc..
int baseDP = targetLevel * calculateRankMultipler(npcRank);
int xpPercentage = XPRewardEnum.xpRewardFrom(targetLevel - playerLevel);
return (int) Math.floor(baseDP * xpPercentage * player.getRates().getGroupXpRate() / 100);
}
/**
* Hate based on BOOST_HATE stat
* Now used only from skills, probably need to use for regular attack
*
* @param creature
* @param value
* @return
*/
public static int calculateHate(Creature creature, int value)
{
return (int) Math.round(value * creature.getGameStats().getCurrentStat(StatEnum.BOOST_HATE) / 100f);
}
/**
*
* @param player
* @param target
* @return Damage made to target (-hp value)
*/
public static int calculateBaseDamageToTarget(Creature attacker, Creature target)
{
return calculatePhysicDamageToTarget(attacker, target, 0);
}
/**
*
* @param player
* @param target
* @param skillDamages
* @return Damage made to target (-hp value)
*/
public static int calculatePhysicDamageToTarget(Creature attacker, Creature target, int skillDamages)
{
CreatureGameStats<?> ags = attacker.getGameStats();
CreatureGameStats<?> tgs = target.getGameStats();
int resultDamage = 0;
if (attacker instanceof Player)
{
int totalMin = ags.getCurrentStat(StatEnum.MIN_DAMAGES);
int totalMax = ags.getCurrentStat(StatEnum.MAX_DAMAGES);
int average = Math.round((totalMin + totalMax)/2);
int mainHandAttack = ags.getBaseStat(StatEnum.MAIN_HAND_POWER);
Equipment equipment = ((Player)attacker).getEquipment();
WeaponType weaponType = equipment.getMainHandWeaponType();
if(weaponType != null)
{
if(average < 1)
{
average = 1;
log.warn("Weapon stat MIN_MAX_DAMAGE resulted average zero in main-hand calculation");
log.warn("Weapon ID: " + String.valueOf(equipment.getMainHandWeapon().getItemTemplate().getTemplateId()));
log.warn("MIN_DAMAGE = " + String.valueOf(totalMin));
log.warn("MAX_DAMAGE = " + String.valueOf(totalMax));
}
//TODO move to controller
if(weaponType == WeaponType.BOW)
equipment.useArrow();
int min = Math.round((((mainHandAttack * 100)/ average) * totalMin)/100);
int max = Math.round((((mainHandAttack * 100)/ average) * totalMax)/100);
int base = Rnd.get(min,max);
resultDamage = Math.round((base * (ags.getCurrentStat(StatEnum.POWER) * 0.01f + ags.getStatBonus(StatEnum.MAIN_HAND_POWER) * 0.01f))
+ ags.getStatBonus(StatEnum.MAIN_HAND_POWER) + skillDamages);
}
else //if hand attack
{
int base = Rnd.get(16,20);
resultDamage = Math.round(base * (ags.getCurrentStat(StatEnum.POWER) * 0.01f));
}
//adjusting baseDamages according to attacker and target level
//
resultDamage = adjustDamages(attacker, target, resultDamage);
if(attacker.isInState(CreatureState.POWERSHARD))
{
Item mainHandPowerShard = equipment.getMainHandPowerShard();
if(mainHandPowerShard != null)
{
resultDamage += mainHandPowerShard.getItemTemplate().getWeaponBoost();
equipment.usePowerShard(mainHandPowerShard, 1);
}
}
}
else if(attacker instanceof Summon)
{
int baseDamage = ags.getCurrentStat(StatEnum.MAIN_HAND_POWER);
int max = (int)(baseDamage + baseDamage * attacker.getLevel() / 10);
int min = max - ags.getCurrentStat(StatEnum.MAIN_HAND_POWER);
resultDamage += Rnd.get(min, max);
}
else
{
NpcRank npcRank = ((Npc) attacker).getObjectTemplate().getRank();
double multipler = calculateRankMultipler(npcRank);
double hpGaugeMod = 1+(((Npc) attacker).getObjectTemplate().getHpGauge()/10);
int baseDamage = ags.getCurrentStat(StatEnum.MAIN_HAND_POWER);
int max = (int)((baseDamage * multipler * hpGaugeMod) + ((baseDamage*attacker.getLevel())/10));
int min = max - ags.getCurrentStat(StatEnum.MAIN_HAND_POWER);
resultDamage += Rnd.get(min, max);
}
resultDamage -= Math.round(tgs.getCurrentStat(StatEnum.PHYSICAL_DEFENSE) * 0.10f);
if (resultDamage<=0)
resultDamage=1;
return resultDamage;
}
/**
*
* @param attacker
* @param target
* @return
*/
public static int calculateOffHandPhysicDamageToTarget(Creature attacker, Creature target)
{
CreatureGameStats<?> ags = attacker.getGameStats();
CreatureGameStats<?> tgs = target.getGameStats();
int totalMin = ags.getCurrentStat(StatEnum.MIN_DAMAGES);
int totalMax = ags.getCurrentStat(StatEnum.MAX_DAMAGES);
int average = Math.round((totalMin + totalMax)/2);
int offHandAttack = ags.getBaseStat(StatEnum.OFF_HAND_POWER);
Equipment equipment = ((Player)attacker).getEquipment();
if(average < 1)
{
average = 1;
log.warn("Weapon stat MIN_MAX_DAMAGE resulted average zero in off-hand calculation");
log.warn("Weapon ID: " + String.valueOf(equipment.getOffHandWeapon().getItemTemplate().getTemplateId()));
log.warn("MIN_DAMAGE = " + String.valueOf(totalMin));
log.warn("MAX_DAMAGE = " + String.valueOf(totalMax));
}
int Damage = 0;
int min = Math.round((((offHandAttack * 100)/ average) * totalMin)/100);
int max = Math.round((((offHandAttack * 100)/ average) * totalMax)/100);
int base = Rnd.get(min,max);
Damage = Math.round((base * (ags.getCurrentStat(StatEnum.POWER) * 0.01f + ags.getStatBonus(StatEnum.OFF_HAND_POWER) * 0.01f))
+ ags.getStatBonus(StatEnum.OFF_HAND_POWER));
Damage = adjustDamages(attacker, target, Damage);
if(attacker.isInState(CreatureState.POWERSHARD))
{
Item offHandPowerShard = equipment.getOffHandPowerShard();
if(offHandPowerShard != null)
{
Damage += offHandPowerShard.getItemTemplate().getWeaponBoost();
equipment.usePowerShard(offHandPowerShard, 1);
}
}
Damage -= Math.round(tgs.getCurrentStat(StatEnum.PHYSICAL_DEFENSE) * 0.10f);
for(float i = 0.25f; i <= 1; i+=0.25f)
{
if(Rnd.get(0, 100) < 50)
{
Damage *= i;
break;
}
}
if (Damage<=0)
Damage=1;
return Damage;
}
/**
* @param player
* @param target
* @param skillEffectTemplate
* @return HP damage to target
*/
public static int calculateMagicDamageToTarget(Creature speller, Creature target, int baseDamages, SkillElement element)
{
CreatureGameStats<?> sgs = speller.getGameStats();
CreatureGameStats<?> tgs = target.getGameStats();
int damages = Math.round(baseDamages * ((sgs.getCurrentStat(StatEnum.KNOWLEDGE) / 100f) + (sgs.getCurrentStat(StatEnum.BOOST_MAGICAL_SKILL) / 1000f)));
//adjusting baseDamages according to attacker and target level
//
damages = adjustDamages(speller, target, damages);
// element resist: fire, wind, water, eath
//
// 10 elemental resist ~ 1% reduce of magical baseDamages
//
damages = Math.round(damages * (1 - tgs.getMagicalDefenseFor(element) / 1000f));
// IMPORTANT NOTES
//
// magicalResistance supposed to be counted to EVADE magic, not to reduce damage, only the elementaryDefense it's counted to reduce magic attack
//
// so example if 200 magic resist vs 100 magic accuracy, 200 - 100 = 100/10 = 0.10 or 10% chance of EVADE
//
// damages -= Math.round((elementaryDefense+magicalResistance)*0.60f);
if (damages<=0) {
damages=1;
}
return damages;
}
/**
*
* @param npcRank
* @return
*/
public static int calculateRankMultipler(NpcRank npcRank)
{
//FIXME: to correct formula, have any reference?
int multipler;
switch(npcRank)
{
case JUNK:
multipler = 2;
break;
case NORMAL:
multipler = 2;
break;
case ELITE:
multipler = 3;
break;
case HERO:
multipler = 4;
break;
case LEGENDARY:
multipler = 5;
break;
default:
multipler = 1;
}
return multipler;
}
/**
* adjust baseDamages according to their level || is PVP?
*
* @ref:
*
* @param attacker lvl
* @param target lvl
* @param baseDamages
*
**/
public static int adjustDamages(Creature attacker, Creature target, int Damages) {
int attackerLevel = attacker.getLevel();
int targetLevel = target.getLevel();
int baseDamages = Damages;
//fix this for better monster target condition please
if ( (attacker instanceof Player) && !(target instanceof Player)) {
if(targetLevel > attackerLevel) {
float multipler = 0.0f;
int differ = (targetLevel - attackerLevel);
if( differ <= 2 ) {
return baseDamages;
}
else if( differ > 2 && differ < 10 ) {
multipler = (differ - 2f) / 10f;
baseDamages -= Math.round((baseDamages * multipler));
}
else {
baseDamages -= Math.round((baseDamages * 0.80f));
}
return baseDamages;
}
} //end of damage to monster
//PVP damages is capped of 60% of the actual baseDamage
else if( (attacker instanceof Player) && (target instanceof Player) ) {
baseDamages = Math.round(baseDamages * 0.60f);
float pvpAttackBonus = attacker.getGameStats().getCurrentStat(StatEnum.PVP_ATTACK_RATIO) * 0.001f;
float pvpDefenceBonus = target.getGameStats().getCurrentStat(StatEnum.PVP_DEFEND_RATIO) * 0.001f;
baseDamages = Math.round(baseDamages + (baseDamages * pvpAttackBonus) - (baseDamages * pvpDefenceBonus));
return baseDamages;
}
return baseDamages;
}
/**
* Calculates DODGE chance
*
* @param attacker
* @param attacked
* @return int
*/
public static int calculatePhysicalDodgeRate(Creature attacker, Creature attacked)
{
//check always dodge
if(attacked.getObserveController().checkAttackStatus(AttackStatus.DODGE))
return 100;
int accuracy;
if(attacker instanceof Player && ((Player) attacker).getEquipment().getOffHandWeaponType() != null)
accuracy = Math.round((attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_ACCURACY) + attacker
.getGameStats().getCurrentStat(StatEnum.OFF_HAND_ACCURACY)) / 2);
else
accuracy = attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_ACCURACY);
int dodgeRate = (attacked.getGameStats().getCurrentStat(StatEnum.EVASION) - accuracy) / 10;
// maximal dodge rate
if(dodgeRate > 30)
dodgeRate = 30;
if(dodgeRate <= 0)
return 1;
return dodgeRate;
}
/**
* Calculates PARRY chance
*
* @param attacker
* @param attacked
* @return int
*/
public static int calculatePhysicalParryRate(Creature attacker, Creature attacked)
{
//check always parry
if(attacked.getObserveController().checkAttackStatus(AttackStatus.PARRY))
return 100;
int accuracy;
if(attacker instanceof Player && ((Player) attacker).getEquipment().getOffHandWeaponType() != null)
accuracy = Math.round((attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_ACCURACY) + attacker
.getGameStats().getCurrentStat(StatEnum.OFF_HAND_ACCURACY)) / 2);
else
accuracy = attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_ACCURACY);
int parryRate = (attacked.getGameStats().getCurrentStat(StatEnum.PARRY) - accuracy) / 10;
// maximal parry rate
if(parryRate > 40)
parryRate = 40;
if(parryRate <= 0)
return 1;
return parryRate;
}
/**
* Calculates BLOCK chance
*
* @param attacker
* @param attacked
* @return int
*/
public static int calculatePhysicalBlockRate(Creature attacker, Creature attacked)
{
//check always block
if(attacked.getObserveController().checkAttackStatus(AttackStatus.BLOCK))
return 100;
int accuracy;
if(attacker instanceof Player && ((Player) attacker).getEquipment().getOffHandWeaponType() != null)
accuracy = Math.round((attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_ACCURACY) + attacker
.getGameStats().getCurrentStat(StatEnum.OFF_HAND_ACCURACY)) / 2);
else
accuracy = attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_ACCURACY);
int blockRate = (attacked.getGameStats().getCurrentStat(StatEnum.BLOCK) - accuracy) / 10;
// maximal block rate
if(blockRate > 50)
blockRate = 50;
if(blockRate <= 0)
return 1;
return blockRate;
}
/**
* Calculates CRITICAL chance
*
* @param attacker
* @return double
*/
public static double calculatePhysicalCriticalRate(Creature attacker)
{
int critical;
if(attacker instanceof Player && ((Player) attacker).getEquipment().getOffHandWeaponType() != null)
critical = Math.round((attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_CRITICAL) + attacker
.getGameStats().getCurrentStat(StatEnum.OFF_HAND_CRITICAL)) / 2);
else
critical = attacker.getGameStats().getCurrentStat(StatEnum.MAIN_HAND_CRITICAL);
double criticalRate;
if(critical <= 440)
criticalRate = critical * 0.1f;
else if(critical <= 600)
criticalRate = (440 * 0.1f) + ((critical - 440) * 0.05f);
else
criticalRate = (440 * 0.1f) + (160 * 0.05f) + ((critical - 600) * 0.02f);
// minimal critical rate
if(criticalRate < 1)
criticalRate = 1;
return criticalRate;
}
/**
* Calculates RESIST chance
*
* @param attacker
* @param attacked
* @return int
*/
public static int calculateMagicalResistRate(Creature attacker, Creature attacked)
{
if(attacked.getObserveController().checkAttackStatus(AttackStatus.RESIST))
return 100;
int resistRate = Math.round((attacked.getGameStats().getCurrentStat(StatEnum.MAGICAL_RESIST) - attacker
.getGameStats().getCurrentStat(StatEnum.MAGICAL_ACCURACY)) / 10);
return resistRate;
}
}