/* * This program 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. * * This program 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 * this program. If not, see <http://www.gnu.org/licenses/>. */ package com.l2jserver.gameserver.model.actor.stat; import com.l2jserver.Config; import com.l2jserver.gameserver.model.Elementals; import com.l2jserver.gameserver.model.L2ItemInstance; import com.l2jserver.gameserver.model.L2Skill; import com.l2jserver.gameserver.model.actor.L2Character; import com.l2jserver.gameserver.skills.Calculator; import com.l2jserver.gameserver.skills.Env; import com.l2jserver.gameserver.skills.Stats; import com.l2jserver.gameserver.templates.item.L2Weapon; import com.l2jserver.gameserver.templates.item.L2WeaponType; public class CharStat { // ========================================================= // Data Field private L2Character _activeChar; private long _exp = 0; private int _sp = 0; private byte _level = 1; // ========================================================= // Constructor public CharStat(L2Character activeChar) { _activeChar = activeChar; } // ========================================================= // Method - Public /** * Calculate the new value of the state with modifiers that will be applied * on the targeted L2Character.<BR> * <BR> * * <B><U> Concept</U> :</B><BR> * <BR> * A L2Character owns a table of Calculators called <B>_calculators</B>. * Each Calculator (a calculator per state) own a table of Func object. A * Func object is a mathematic function that permit to calculate the * modifier of a state (ex : REGENERATE_HP_RATE...) : <BR> * <BR> * * FuncAtkAccuracy -> Math.sqrt(_player.getDEX())*6+_player.getLevel()<BR> * <BR> * * When the calc method of a calculator is launched, each mathematic * function is called according to its priority <B>_order</B>. Indeed, Func * with lowest priority order is executed firsta and Funcs with the same * order are executed in unspecified order. The result of the calculation is * stored in the value property of an Env class instance.<BR> * <BR> * * @param stat * The stat to calculate the new value with modifiers * @param init * The initial value of the stat before applying modifiers * @param target * The L2Charcater whose properties will be used in the * calculation (ex : CON, INT...) * @param skill * The L2Skill whose properties will be used in the calculation * (ex : Level...) * */ public final double calcStat(Stats stat, double init, L2Character target, L2Skill skill) { if (_activeChar == null || stat == null) return init; int id = stat.ordinal(); Calculator c = _activeChar.getCalculators()[id]; // If no Func object found, no modifier is applied if (c == null || c.size() == 0) return init; // Create and init an Env object to pass parameters to the Calculator Env env = new Env(); env.player = _activeChar; env.target = target; env.skill = skill; env.value = init; // Launch the calculation c.calc(env); // avoid some troubles with negative stats (some stats should never be negative) if (env.value <= 0) { switch(stat) { case MAX_HP: case MAX_MP: case MAX_CP: case MAGIC_DEFENCE: case POWER_DEFENCE: case POWER_ATTACK: case MAGIC_ATTACK: case POWER_ATTACK_SPEED: case MAGIC_ATTACK_SPEED: case SHIELD_DEFENCE: case STAT_CON: case STAT_DEX: case STAT_INT: case STAT_MEN: case STAT_STR: case STAT_WIT: env.value = 1; } } return env.value; } // ========================================================= // Method - Private // ========================================================= // Property - Public /** * Return the Accuracy (base+modifier) of the L2Character in function of the * Weapon Expertise Penalty. */ public int getAccuracy() { if (_activeChar == null) return 0; return (int) Math.round(calcStat(Stats.ACCURACY_COMBAT, 0, null, null)); } public L2Character getActiveChar() { return _activeChar; } /** * Return the Attack Speed multiplier (base+modifier) of the L2Character to * get proper animations. */ public final float getAttackSpeedMultiplier() { if (_activeChar == null) return 1; return (float) ((1.1) * getPAtkSpd() / _activeChar.getTemplate().basePAtkSpd); } /** Return the CON of the L2Character (base+modifier). */ public final int getCON() { if (_activeChar == null) return 1; return (int) calcStat(Stats.STAT_CON, _activeChar.getTemplate().baseCON, null, null); } /** Return the Critical Damage rate (base+modifier) of the L2Character. */ public final double getCriticalDmg(L2Character target, double init) { return calcStat(Stats.CRITICAL_DAMAGE, init, target, null); } /** Return the Critical Hit rate (base+modifier) of the L2Character. */ public int getCriticalHit(L2Character target, L2Skill skill) { if (_activeChar == null) return 1; int criticalHit = (int) Math.round(calcStat(Stats.CRITICAL_RATE, _activeChar.getTemplate().baseCritRate, target, skill)*10.0 + 0.5); criticalHit /= 10; // Set a cap of Critical Hit at 500 if (criticalHit > Config.MAX_PCRIT_RATE) criticalHit = Config.MAX_PCRIT_RATE; return criticalHit; } /** Return the DEX of the L2Character (base+modifier). */ public final int getDEX() { if (_activeChar == null) return 1; return (int) calcStat(Stats.STAT_DEX, _activeChar.getTemplate().baseDEX, null, null); } /** Return the Attack Evasion rate (base+modifier) of the L2Character. */ public int getEvasionRate(L2Character target) { if (_activeChar == null) return 1; int val = (int) Math.round(calcStat(Stats.EVASION_RATE, 0, target, null)); if (val > Config.MAX_EVASION && !_activeChar.isGM()) val = Config.MAX_EVASION; return val; } public long getExp() { return _exp; } public void setExp(long value) { _exp = value; } /** Return the INT of the L2Character (base+modifier). */ public int getINT() { if (_activeChar == null) return 1; return (int) calcStat(Stats.STAT_INT, _activeChar.getTemplate().baseINT, null, null); } public byte getLevel() { return _level; } public void setLevel(byte value) { _level = value; } /** Return the Magical Attack range (base+modifier) of the L2Character. */ public final int getMagicalAttackRange(L2Skill skill) { if (_activeChar == null) return 1; if (skill != null) return (int) calcStat(Stats.MAGIC_ATTACK_RANGE, skill.getCastRange(), null, skill); return _activeChar.getTemplate().baseAtkRange; } public int getMaxCp() { if (_activeChar == null) return 1; return (int) calcStat(Stats.MAX_CP, _activeChar.getTemplate().baseCpMax, null, null); } public int getMaxRecoverableCp() { if (_activeChar == null) return 1; return (int) calcStat(Stats.MAX_RECOVERABLE_CP, getMaxCp(), null, null); } public int getMaxHp() { if (_activeChar == null) return 1; return (int) calcStat(Stats.LIMIT_HP, getMaxVisibleHp(), null, null); } public int getMaxVisibleHp() { if (_activeChar == null) return 1; float bonusHp = 1; if (_activeChar.isRaid() || _activeChar.isRaidMinion()) bonusHp *= Config.RAID_MAXHP_MULTIPLIER; return (int) calcStat(Stats.MAX_HP, _activeChar.getTemplate().baseHpMax * bonusHp, null, null); } public int getMaxRecoverableHp() { if (_activeChar == null) return 1; return (int) calcStat(Stats.MAX_RECOVERABLE_HP, getMaxVisibleHp(), null, null); } public int getMaxMp() { if (_activeChar == null) return 1; return (int) calcStat(Stats.MAX_MP, _activeChar.getTemplate().baseMpMax, null, null); } public int getMaxRecoverableMp() { if (_activeChar == null) return 1; return (int) calcStat(Stats.MAX_RECOVERABLE_MP, getMaxMp(), null, null); } /** * Return the MAtk (base+modifier) of the L2Character for a skill used in * function of abnormal effects in progress.<BR> * <BR> * * <B><U> Example of use </U> :</B><BR> * <BR> * <li> Calculate Magic damage </li> * <BR> * <BR> * * @param target * The L2Character targeted by the skill * @param skill * The L2Skill used against the target */ public int getMAtk(L2Character target, L2Skill skill) { if (_activeChar == null) return 1; float bonusAtk = 1; if (Config.L2JMOD_CHAMPION_ENABLE && _activeChar.isChampion()) bonusAtk = Config.L2JMOD_CHAMPION_ATK; if (_activeChar.isRaid() || _activeChar.isRaidMinion()) bonusAtk *= Config.RAID_MATTACK_MULTIPLIER; double attack = _activeChar.getTemplate().baseMAtk * bonusAtk; // Get the skill type to calculate its effect in function of base stats // of the L2Character target Stats stat = skill == null ? null : skill.getStat(); if (stat != null) { switch (stat) { case AGGRESSION: attack += _activeChar.getTemplate().baseAggression; break; case BLEED: attack += _activeChar.getTemplate().baseBleed; break; case POISON: attack += _activeChar.getTemplate().basePoison; break; case STUN: attack += _activeChar.getTemplate().baseStun; break; case ROOT: attack += _activeChar.getTemplate().baseRoot; break; case MOVEMENT: attack += _activeChar.getTemplate().baseMovement; break; case CONFUSION: attack += _activeChar.getTemplate().baseConfusion; break; case SLEEP: attack += _activeChar.getTemplate().baseSleep; break; } } // Add the power of the skill to the attack effect if (skill != null) attack += skill.getPower(); // Calculate modifiers Magic Attack return (int) calcStat(Stats.MAGIC_ATTACK, attack, target, skill); } /** * Return the MAtk Speed (base+modifier) of the L2Character in function of * the Armour Expertise Penalty. */ public int getMAtkSpd() { if (_activeChar == null) return 1; float bonusSpdAtk = 1; if (Config.L2JMOD_CHAMPION_ENABLE && _activeChar.isChampion()) bonusSpdAtk = Config.L2JMOD_CHAMPION_SPD_ATK; double val = calcStat(Stats.MAGIC_ATTACK_SPEED, _activeChar.getTemplate().baseMAtkSpd * bonusSpdAtk, null, null); if (val > Config.MAX_MATK_SPEED && !_activeChar.isGM()) val = Config.MAX_MATK_SPEED; return (int) val; } /** Return the Magic Critical Hit rate (base+modifier) of the L2Character. */ public final int getMCriticalHit(L2Character target, L2Skill skill) { if (_activeChar == null) return 1; double mrate = calcStat(Stats.MCRITICAL_RATE, _activeChar.getTemplate().baseMCritRate, target, skill); if(mrate > Config.MAX_MCRIT_RATE) mrate = Config.MAX_MCRIT_RATE; return (int) mrate; } /** * Return the MDef (base+modifier) of the L2Character against a skill in * function of abnormal effects in progress.<BR> * <BR> * * <B><U> Example of use </U> :</B><BR> * <BR> * <li> Calculate Magic damage </li> * <BR> * * @param target * The L2Character targeted by the skill * @param skill * The L2Skill used against the target */ public int getMDef(L2Character target, L2Skill skill) { if (_activeChar == null) return 1; // Get the base MAtk of the L2Character double defence = _activeChar.getTemplate().baseMDef; // Calculate modifier for Raid Bosses if (_activeChar.isRaid() || _activeChar.isRaidMinion()) defence *= Config.RAID_MDEFENCE_MULTIPLIER; // Calculate modifiers Magic Attack return (int) calcStat(Stats.MAGIC_DEFENCE, defence, target, skill); } /** Return the MEN of the L2Character (base+modifier). */ public final int getMEN() { if (_activeChar == null) return 1; return (int) calcStat(Stats.STAT_MEN, _activeChar.getTemplate().baseMEN, null, null); } public float getMovementSpeedMultiplier() { if (_activeChar == null) return 1; return getRunSpeed() / (float) _activeChar.getTemplate().baseRunSpd; } /** * Return the RunSpeed (base+modifier) or WalkSpeed (base+modifier) of the * L2Character in function of the movement type. */ public float getMoveSpeed() { if (_activeChar == null) return 1; if (_activeChar.isRunning()) return getRunSpeed(); return getWalkSpeed(); } /** Return the MReuse rate (base+modifier) of the L2Character. */ public final double getMReuseRate(L2Skill skill) { if (_activeChar == null) return 1; return calcStat(Stats.MAGIC_REUSE_RATE, _activeChar.getTemplate().baseMReuseRate, null, skill); } /** Return the PReuse rate (base+modifier) of the L2Character. */ public final double getPReuseRate(L2Skill skill) { if (_activeChar == null) return 1; return calcStat(Stats.P_REUSE, _activeChar.getTemplate().baseMReuseRate, null, skill); } /** Return the PAtk (base+modifier) of the L2Character. */ public int getPAtk(L2Character target) { if (_activeChar == null) return 1; float bonusAtk = 1; if (Config.L2JMOD_CHAMPION_ENABLE && _activeChar.isChampion()) bonusAtk = Config.L2JMOD_CHAMPION_ATK; if (_activeChar.isRaid() || _activeChar.isRaidMinion()) bonusAtk *= Config.RAID_PATTACK_MULTIPLIER; return (int) calcStat(Stats.POWER_ATTACK, _activeChar.getTemplate().basePAtk * bonusAtk, target, null); } /** Return the PAtk Modifier against animals. */ public final double getPAtkAnimals(L2Character target) { return calcStat(Stats.PATK_ANIMALS, 1, target, null); } /** Return the PAtk Modifier against dragons. */ public final double getPAtkDragons(L2Character target) { return calcStat(Stats.PATK_DRAGONS, 1, target, null); } /** Return the PAtk Modifier against insects. */ public final double getPAtkInsects(L2Character target) { return calcStat(Stats.PATK_INSECTS, 1, target, null); } /** Return the PAtk Modifier against monsters. */ public final double getPAtkMonsters(L2Character target) { return calcStat(Stats.PATK_MONSTERS, 1, target, null); } /** Return the PAtk Modifier against plants. */ public final double getPAtkPlants(L2Character target) { return calcStat(Stats.PATK_PLANTS, 1, target, null); } /** Return the PAtk Modifier against giants. */ public final double getPAtkGiants(L2Character target) { return calcStat(Stats.PATK_GIANTS, 1, target, null); } /** Return the PAtk Modifier against magic creatures */ public final double getPAtkMagicCreatures(L2Character target) { return calcStat(Stats.PATK_MCREATURES, 1, target, null); } /** * Return the PAtk Speed (base+modifier) of the L2Character in function of * the Armour Expertise Penalty. */ public int getPAtkSpd() { if (_activeChar == null) return 1; float bonusAtk = 1; if (Config.L2JMOD_CHAMPION_ENABLE && _activeChar.isChampion()) bonusAtk = Config.L2JMOD_CHAMPION_SPD_ATK; int val = (int) Math.round(calcStat(Stats.POWER_ATTACK_SPEED, _activeChar.getTemplate().basePAtkSpd * bonusAtk, null, null)); return val; } /** Return the PDef Modifier against animals. */ public final double getPDefAnimals(L2Character target) { return calcStat(Stats.PDEF_ANIMALS, 1, target, null); } /** Return the PDef Modifier against dragons. */ public final double getPDefDragons(L2Character target) { return calcStat(Stats.PDEF_DRAGONS, 1, target, null); } /** Return the PDef Modifier against insects. */ public final double getPDefInsects(L2Character target) { return calcStat(Stats.PDEF_INSECTS, 1, target, null); } /** Return the PDef Modifier against monsters. */ public final double getPDefMonsters(L2Character target) { return calcStat(Stats.PDEF_MONSTERS, 1, target, null); } /** Return the PDef Modifier against plants. */ public final double getPDefPlants(L2Character target) { return calcStat(Stats.PDEF_PLANTS, 1, target, null); } /** Return the PDef Modifier against giants. */ public final double getPDefGiants(L2Character target) { return calcStat(Stats.PDEF_GIANTS, 1, target, null); } /** Return the PDef Modifier against giants. */ public final double getPDefMagicCreatures(L2Character target) { return calcStat(Stats.PDEF_MCREATURES, 1, target, null); } /** Return the PDef (base+modifier) of the L2Character. */ public int getPDef(L2Character target) { if (_activeChar == null) return 1; return (int) calcStat(Stats.POWER_DEFENCE, (_activeChar.isRaid()) ? _activeChar.getTemplate().basePDef * Config.RAID_PDEFENCE_MULTIPLIER : _activeChar.getTemplate().basePDef, target, null); } /** Return the Physical Attack range (base+modifier) of the L2Character. */ public final int getPhysicalAttackRange() { if (_activeChar == null) return 1; if (_activeChar.isTransformed()) return _activeChar.getTemplate().baseAtkRange; // Polearm handled here for now. Basically L2PcInstance could have a function // similar to FuncBowAtkRange and NPC are defined in DP. L2Weapon weaponItem = _activeChar.getActiveWeaponItem(); if (weaponItem != null && weaponItem.getItemType() == L2WeaponType.POLE) return (int) calcStat(Stats.POWER_ATTACK_RANGE, 66, null, null); return (int) calcStat(Stats.POWER_ATTACK_RANGE, _activeChar.getTemplate().baseAtkRange, null, null); } /** Return the weapon reuse modifier */ public final double getWeaponReuseModifier(L2Character target) { return calcStat(Stats.ATK_REUSE, 1, target, null); } /** * Return the RunSpeed (base+modifier) of the L2Character in function of the * Armour Expertise Penalty. */ public int getRunSpeed() { if (_activeChar == null) return 1; // err we should be adding TO the persons run speed // not making it a constant double baseRunSpd = _activeChar.getTemplate().baseRunSpd; if (baseRunSpd == 0) return 0; return (int) Math.round(calcStat(Stats.RUN_SPEED, baseRunSpd, null, null)); } /** Return the ShieldDef rate (base+modifier) of the L2Character. */ public final int getShldDef() { return (int) calcStat(Stats.SHIELD_DEFENCE, 0, null, null); } public int getSp() { return _sp; } public void setSp(int value) { _sp = value; } /** Return the STR of the L2Character (base+modifier). */ public final int getSTR() { if (_activeChar == null) return 1; return (int) calcStat(Stats.STAT_STR, _activeChar.getTemplate().baseSTR, null, null); } /** Return the WalkSpeed (base+modifier) of the L2Character. */ public int getWalkSpeed() { if (_activeChar == null) return 1; double baseWalkSpd = _activeChar.getTemplate().baseWalkSpd; if (baseWalkSpd == 0) return 0; return (int) calcStat(Stats.WALK_SPEED, baseWalkSpd, null, null); } /** Return the WIT of the L2Character (base+modifier). */ public final int getWIT() { if (_activeChar == null) return 1; return (int) calcStat(Stats.STAT_WIT, _activeChar.getTemplate().baseWIT, null, null); } /** Return the mpConsume. */ public final int getMpConsume(L2Skill skill) { if (skill == null) return 1; double mpConsume = skill.getMpConsume(); if (skill.isDance()) { if (Config.DANCE_CONSUME_ADDITIONAL_MP && _activeChar != null && _activeChar.getDanceCount() > 0) mpConsume += _activeChar.getDanceCount() * skill.getNextDanceMpCost(); } mpConsume = calcStat(Stats.MP_CONSUME, mpConsume, null, skill); if (skill.isDance()) return (int)calcStat(Stats.DANCE_MP_CONSUME_RATE, mpConsume, null, null); else if (skill.isMagic()) return (int)calcStat(Stats.MAGICAL_MP_CONSUME_RATE, mpConsume, null, null); else return (int)calcStat(Stats.PHYSICAL_MP_CONSUME_RATE, mpConsume, null, null); } /** Return the mpInitialConsume. */ public final int getMpInitialConsume(L2Skill skill) { if (skill == null) return 1; double mpConsume = calcStat(Stats.MP_CONSUME, skill.getMpInitialConsume(), null, skill); if (skill.isDance()) return (int)calcStat(Stats.DANCE_MP_CONSUME_RATE, mpConsume, null, null); else if (skill.isMagic()) return (int)calcStat(Stats.MAGICAL_MP_CONSUME_RATE, mpConsume, null, null); else return (int)calcStat(Stats.PHYSICAL_MP_CONSUME_RATE, mpConsume, null, null); } public byte getAttackElement() { L2ItemInstance weaponInstance = _activeChar.getActiveWeaponInstance(); // 1st order - weapon element if (weaponInstance != null && weaponInstance.getAttackElementType() >= 0 ) return weaponInstance.getAttackElementType(); // temp fix starts int tempVal =0, stats[] = { 0, 0, 0, 0, 0, 0 }; byte returnVal = -2; stats[0] = (int) calcStat(Stats.FIRE_POWER, _activeChar.getTemplate().baseFire, null, null); stats[1] = (int) calcStat(Stats.WATER_POWER, _activeChar.getTemplate().baseWater, null, null); stats[2] = (int) calcStat(Stats.WIND_POWER, _activeChar.getTemplate().baseWind, null, null); stats[3] = (int) calcStat(Stats.EARTH_POWER, _activeChar.getTemplate().baseEarth, null, null); stats[4] = (int) calcStat(Stats.HOLY_POWER, _activeChar.getTemplate().baseHoly, null, null); stats[5] = (int) calcStat(Stats.DARK_POWER, _activeChar.getTemplate().baseDark, null, null); for (byte x = 0; x < 6; x++) { if (stats[x] > tempVal) { returnVal = x; tempVal = stats[x]; } } return returnVal; // temp fix ends /* * uncomment me once deadlocks in getAllEffects() fixed return _activeChar.getElementIdFromEffects(); */ } public int getAttackElementValue(byte attackAttribute) { switch (attackAttribute) { case Elementals.FIRE: return (int) calcStat(Stats.FIRE_POWER, _activeChar.getTemplate().baseFire, null, null); case Elementals.WATER: return (int) calcStat(Stats.WATER_POWER, _activeChar.getTemplate().baseWater, null, null); case Elementals.WIND: return (int) calcStat(Stats.WIND_POWER, _activeChar.getTemplate().baseWind, null, null); case Elementals.EARTH: return (int) calcStat(Stats.EARTH_POWER, _activeChar.getTemplate().baseEarth, null, null); case Elementals.HOLY: return (int) calcStat(Stats.HOLY_POWER, _activeChar.getTemplate().baseHoly, null, null); case Elementals.DARK: return (int) calcStat(Stats.DARK_POWER, _activeChar.getTemplate().baseDark, null, null); default: return 0; } } public int getDefenseElementValue(byte defenseAttribute) { switch (defenseAttribute) { case Elementals.FIRE: return (int) calcStat(Stats.FIRE_RES, _activeChar.getTemplate().baseFireRes, null, null); case Elementals.WATER: return (int) calcStat(Stats.WATER_RES, _activeChar.getTemplate().baseWaterRes, null, null); case Elementals.WIND: return (int) calcStat(Stats.WIND_RES, _activeChar.getTemplate().baseWindRes, null, null); case Elementals.EARTH: return (int) calcStat(Stats.EARTH_RES, _activeChar.getTemplate().baseEarthRes, null, null); case Elementals.HOLY: return (int) calcStat(Stats.HOLY_RES, _activeChar.getTemplate().baseHolyRes, null, null); case Elementals.DARK: return (int) calcStat(Stats.DARK_RES, _activeChar.getTemplate().baseDarkRes, null, null); default: return 0; } } }