/* * 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 silentium.gameserver.skills; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import silentium.commons.utils.Rnd; import silentium.commons.utils.StringUtil; import silentium.gameserver.configs.MainConfig; import silentium.gameserver.configs.NPCConfig; import silentium.gameserver.configs.PlayersConfig; import silentium.gameserver.instancemanager.ClanHallManager; import silentium.gameserver.instancemanager.SiegeManager; import silentium.gameserver.instancemanager.ZoneManager; import silentium.gameserver.model.L2SiegeClan; import silentium.gameserver.model.L2Skill; import silentium.gameserver.model.actor.L2Attackable; import silentium.gameserver.model.actor.L2Character; import silentium.gameserver.model.actor.L2Npc; import silentium.gameserver.model.actor.L2Playable; import silentium.gameserver.model.actor.instance.L2CubicInstance; import silentium.gameserver.model.actor.instance.L2DoorInstance; import silentium.gameserver.model.actor.instance.L2PcInstance; import silentium.gameserver.model.entity.ClanHall; import silentium.gameserver.model.entity.Siege; import silentium.gameserver.model.entity.sevensigns.SevenSigns; import silentium.gameserver.model.entity.sevensigns.SevenSignsFestival; import silentium.gameserver.model.zone.type.L2MotherTreeZone; import silentium.gameserver.network.SystemMessageId; import silentium.gameserver.network.serverpackets.SystemMessage; import silentium.gameserver.skills.effects.EffectTemplate; import silentium.gameserver.skills.funcs.*; import silentium.gameserver.templates.item.L2Armor; import silentium.gameserver.templates.item.L2Item; import silentium.gameserver.templates.item.L2Weapon; import silentium.gameserver.templates.item.L2WeaponType; import silentium.gameserver.templates.skills.L2SkillType; import silentium.gameserver.utils.Util; /** * Global calculations, can be modified by server admins */ public final class Formulas { protected static final Logger _log = LoggerFactory.getLogger(Formulas.class.getName()); private static final int HP_REGENERATE_PERIOD = 3000; // 3 secs public static final byte SHIELD_DEFENSE_FAILED = 0; // no shield defense public static final byte SHIELD_DEFENSE_SUCCEED = 1; // normal shield defense public static final byte SHIELD_DEFENSE_PERFECT_BLOCK = 2; // perfect block public static final byte SKILL_REFLECT_FAILED = 0; // no reflect public static final byte SKILL_REFLECT_SUCCEED = 1; // normal reflect, some damage reflected some other not public static final byte SKILL_REFLECT_VENGEANCE = 2; // 100% of the damage affect both private static final byte MELEE_ATTACK_RANGE = 40; public static final int MAX_STAT_VALUE = 100; public static final int BASENPCSTR = 40; public static final int BASENPCCON = 43; public static final int BASENPCDEX = 30; public static final int BASENPCINT = 21; public static final int BASENPCWIT = 20; public static final int BASENPCMEN = 20; private static final double[] STRCompute = new double[] { 1.036, 34.845 }; private static final double[] INTCompute = new double[] { 1.020, 31.375 }; private static final double[] DEXCompute = new double[] { 1.009, 19.360 }; private static final double[] WITCompute = new double[] { 1.050, 20.000 }; private static final double[] CONCompute = new double[] { 1.030, 27.632 }; private static final double[] MENCompute = new double[] { 1.010, -0.060 }; public static final double[] WITbonus = new double[MAX_STAT_VALUE]; public static final double[] MENbonus = new double[MAX_STAT_VALUE]; public static final double[] INTbonus = new double[MAX_STAT_VALUE]; public static final double[] STRbonus = new double[MAX_STAT_VALUE]; public static final double[] DEXbonus = new double[MAX_STAT_VALUE]; public static final double[] CONbonus = new double[MAX_STAT_VALUE]; protected static final double[] sqrtMENbonus = new double[MAX_STAT_VALUE]; protected static final double[] sqrtCONbonus = new double[MAX_STAT_VALUE]; static { for (int i = 0; i < STRbonus.length; i++) STRbonus[i] = Math.floor(Math.pow(STRCompute[0], i - STRCompute[1]) * 100 + .5d) / 100; for (int i = 0; i < INTbonus.length; i++) INTbonus[i] = Math.floor(Math.pow(INTCompute[0], i - INTCompute[1]) * 100 + .5d) / 100; for (int i = 0; i < DEXbonus.length; i++) DEXbonus[i] = Math.floor(Math.pow(DEXCompute[0], i - DEXCompute[1]) * 100 + .5d) / 100; for (int i = 0; i < WITbonus.length; i++) WITbonus[i] = Math.floor(Math.pow(WITCompute[0], i - WITCompute[1]) * 100 + .5d) / 100; for (int i = 0; i < CONbonus.length; i++) CONbonus[i] = Math.floor(Math.pow(CONCompute[0], i - CONCompute[1]) * 100 + .5d) / 100; for (int i = 0; i < MENbonus.length; i++) MENbonus[i] = Math.floor(Math.pow(MENCompute[0], i - MENCompute[1]) * 100 + .5d) / 100; // Precompute square root values for (int i = 0; i < sqrtCONbonus.length; i++) sqrtCONbonus[i] = Math.sqrt(CONbonus[i]); for (int i = 0; i < sqrtMENbonus.length; i++) sqrtMENbonus[i] = Math.sqrt(MENbonus[i]); } /** * @param cha * The character to make checks on. * @return the period between 2 regenerations task (3s for L2Character, 5 min for L2DoorInstance). */ public static int getRegeneratePeriod(L2Character cha) { if (cha instanceof L2DoorInstance) return HP_REGENERATE_PERIOD * 100; // 5 mins return HP_REGENERATE_PERIOD; // 3s } /** * <B><U> Concept</U> :</B><BR> * <BR> * A calculator is created to manage and dynamically calculate the effect of a character property (ex : MAX_HP, REGENERATE_HP_RATE...). In * fact, each calculator is a table of Func object in which each Func represents a mathematic function : <BR> * <BR> * To reduce cache memory use, L2Npcs who don't have skills share the same Calculator set called <B>NPC_STD_CALCULATOR</B>.<BR> * <BR> * * @return the standard NPC Calculator set. */ public static Calculator[] getStdNPCCalculators() { Calculator[] std = new Calculator[Stats.NUM_STATS]; std[Stats.ACCURACY_COMBAT.ordinal()] = new Calculator(); std[Stats.ACCURACY_COMBAT.ordinal()].addFunc(FuncAtkAccuracy.getInstance()); std[Stats.EVASION_RATE.ordinal()] = new Calculator(); std[Stats.EVASION_RATE.ordinal()].addFunc(FuncAtkEvasion.getInstance()); return std; } /** * Add basics Func objects to L2PcInstance and L2Summon.<BR> * <BR> * <B><U> Concept</U> :</B><BR> * <BR> * A calculator is created to manage and dynamically calculate the effect of a character property (ex : MAX_HP, REGENERATE_HP_RATE...). In * fact, each calculator is a table of Func object in which each Func represents a mathematic function : <BR> * <BR> * FuncAtkAccuracy -> Math.sqrt(_player.getDEX())*6+_player.getLevel()<BR> * <BR> * * @param cha * L2PcInstance or L2Summon that must obtain basic Func objects */ public static void addFuncsToNewCharacter(L2Character cha) { // Summons and players. if (cha instanceof L2Playable) { cha.addStatFunc(FuncMaxHpMul.getInstance()); cha.addStatFunc(FuncMaxMpMul.getInstance()); cha.addStatFunc(FuncPAtkMod.getInstance()); cha.addStatFunc(FuncMAtkMod.getInstance()); cha.addStatFunc(FuncPDefMod.getInstance()); cha.addStatFunc(FuncMDefMod.getInstance()); cha.addStatFunc(FuncAtkCritical.getInstance()); cha.addStatFunc(FuncMAtkCritical.getInstance()); cha.addStatFunc(FuncAtkAccuracy.getInstance()); cha.addStatFunc(FuncAtkEvasion.getInstance()); cha.addStatFunc(FuncPAtkSpeed.getInstance()); cha.addStatFunc(FuncMAtkSpeed.getInstance()); cha.addStatFunc(FuncMoveSpeed.getInstance()); // Players only. if (cha instanceof L2PcInstance) { cha.addStatFunc(FuncMaxHpAdd.getInstance()); cha.addStatFunc(FuncMaxCpAdd.getInstance()); cha.addStatFunc(FuncMaxCpMul.getInstance()); cha.addStatFunc(FuncMaxMpAdd.getInstance()); cha.addStatFunc(FuncBowAtkRange.getInstance()); cha.addStatFunc(FuncHennaSTR.getInstance()); cha.addStatFunc(FuncHennaDEX.getInstance()); cha.addStatFunc(FuncHennaINT.getInstance()); cha.addStatFunc(FuncHennaMEN.getInstance()); cha.addStatFunc(FuncHennaCON.getInstance()); cha.addStatFunc(FuncHennaWIT.getInstance()); } } } /** * @param cha * The character to make checks on. * @return the HP regen rate (base + modifiers). */ public static final double calcHpRegen(L2Character cha) { double init = cha.getTemplate().getBaseHpReg(); double hpRegenMultiplier = cha.isRaid() ? NPCConfig.RAID_HP_REGEN_MULTIPLIER : PlayersConfig.HP_REGEN_MULTIPLIER; double hpRegenBonus = 0; if (NPCConfig.CHAMPION_ENABLE && cha.isChampion()) hpRegenMultiplier *= NPCConfig.CHAMPION_HP_REGEN; if (cha instanceof L2PcInstance) { L2PcInstance player = (L2PcInstance) cha; // Calculate correct baseHpReg value for certain level of PC init += (player.getLevel() > 10) ? ((player.getLevel() - 1) / 10.0) : 0.5; // SevenSigns Festival modifier if (SevenSignsFestival.getInstance().isFestivalInProgress() && player.isFestivalParticipant()) hpRegenMultiplier *= calcFestivalRegenModifier(player); else { double siegeModifier = calcSiegeRegenModifer(player); if (siegeModifier > 0) hpRegenMultiplier *= siegeModifier; } if (player.isInsideZone(L2Character.ZONE_CLANHALL) && player.getClan() != null) { int clanHallIndex = player.getClan().getHideoutId(); if (clanHallIndex > 0) { ClanHall clansHall = ClanHallManager.getInstance().getClanHallById(clanHallIndex); if (clansHall != null) if (clansHall.getFunction(ClanHall.FUNC_RESTORE_HP) != null) hpRegenMultiplier *= 1 + clansHall.getFunction(ClanHall.FUNC_RESTORE_HP).getLvl() / 100; } } // Mother Tree effect is calculated at last if (player.isInsideZone(L2Character.ZONE_MOTHERTREE)) { L2MotherTreeZone zone = ZoneManager.getInstance().getZone(player, L2MotherTreeZone.class); int hpBonus = zone == null ? 0 : zone.getHpRegenBonus(); hpRegenBonus += hpBonus; } // Calculate Movement bonus if (player.isSitting()) hpRegenMultiplier *= 1.5; // Sitting else if (!player.isMoving()) hpRegenMultiplier *= 1.1; // Staying else if (player.isRunning()) hpRegenMultiplier *= 0.7; // Running // Add CON bonus init *= cha.getLevelMod() * Formulas.CONbonus[cha.getCON()]; } if (init < 1) init = 1; return cha.calcStat(Stats.REGENERATE_HP_RATE, init, null, null) * hpRegenMultiplier + hpRegenBonus; } /** * @param cha * The character to make checks on. * @return the MP regen rate (base + modifiers). */ public static final double calcMpRegen(L2Character cha) { double init = cha.getTemplate().getBaseMpReg(); double mpRegenMultiplier = cha.isRaid() ? NPCConfig.RAID_MP_REGEN_MULTIPLIER : PlayersConfig.MP_REGEN_MULTIPLIER; double mpRegenBonus = 0; if (cha instanceof L2PcInstance) { L2PcInstance player = (L2PcInstance) cha; // Calculate correct baseMpReg value for certain level of PC init += 0.3 * ((player.getLevel() - 1) / 10.0); // SevenSigns Festival modifier if (SevenSignsFestival.getInstance().isFestivalInProgress() && player.isFestivalParticipant()) mpRegenMultiplier *= calcFestivalRegenModifier(player); // Mother Tree effect is calculated at last if (player.isInsideZone(L2Character.ZONE_MOTHERTREE)) { L2MotherTreeZone zone = ZoneManager.getInstance().getZone(player, L2MotherTreeZone.class); int mpBonus = zone == null ? 0 : zone.getMpRegenBonus(); mpRegenBonus += mpBonus; } if (player.isInsideZone(L2Character.ZONE_CLANHALL) && player.getClan() != null) { int clanHallIndex = player.getClan().getHideoutId(); if (clanHallIndex > 0) { ClanHall clansHall = ClanHallManager.getInstance().getClanHallById(clanHallIndex); if (clansHall != null) if (clansHall.getFunction(ClanHall.FUNC_RESTORE_MP) != null) mpRegenMultiplier *= 1 + clansHall.getFunction(ClanHall.FUNC_RESTORE_MP).getLvl() / 100; } } // Calculate Movement bonus if (player.isSitting()) mpRegenMultiplier *= 1.5; // Sitting else if (!player.isMoving()) mpRegenMultiplier *= 1.1; // Staying else if (player.isRunning()) mpRegenMultiplier *= 0.7; // Running // Add MEN bonus init *= cha.getLevelMod() * Formulas.MENbonus[cha.getMEN()]; } if (init < 1) init = 1; return cha.calcStat(Stats.REGENERATE_MP_RATE, init, null, null) * mpRegenMultiplier + mpRegenBonus; } /** * @param cha * The character to make checks on. * @return the CP regen rate (base + modifiers). */ public static final double calcCpRegen(L2Character cha) { double init = cha.getTemplate().getBaseHpReg(); double cpRegenMultiplier = PlayersConfig.CP_REGEN_MULTIPLIER; double cpRegenBonus = 0; if (cha instanceof L2PcInstance) { L2PcInstance player = (L2PcInstance) cha; // Calculate correct baseHpReg value for certain level of PC init += (player.getLevel() > 10) ? ((player.getLevel() - 1) / 10.0) : 0.5; // Calculate Movement bonus if (player.isSitting()) cpRegenMultiplier *= 1.5; // Sitting else if (!player.isMoving()) cpRegenMultiplier *= 1.1; // Staying else if (player.isRunning()) cpRegenMultiplier *= 0.7; // Running } else { // Calculate Movement bonus if (!cha.isMoving()) cpRegenMultiplier *= 1.1; // Staying else if (cha.isRunning()) cpRegenMultiplier *= 0.7; // Running } // Apply CON bonus init *= cha.getLevelMod() * Formulas.CONbonus[cha.getCON()]; if (init < 1) init = 1; return cha.calcStat(Stats.REGENERATE_CP_RATE, init, null, null) * cpRegenMultiplier + cpRegenBonus; } public static final double calcFestivalRegenModifier(L2PcInstance activeChar) { final int[] festivalInfo = SevenSignsFestival.getInstance().getFestivalForPlayer(activeChar); final int oracle = festivalInfo[0]; final int festivalId = festivalInfo[1]; int[] festivalCenter; // If the player isn't found in the festival, leave the regen rate as it is. if (festivalId < 0) return 0; // Retrieve the X and Y coords for the center of the festival arena the player is in. if (oracle == SevenSigns.CABAL_DAWN) festivalCenter = SevenSignsFestival.FESTIVAL_DAWN_PLAYER_SPAWNS[festivalId]; else festivalCenter = SevenSignsFestival.FESTIVAL_DUSK_PLAYER_SPAWNS[festivalId]; // Check the distance between the player and the player spawn point, in the center of the arena. double distToCenter = activeChar.getPlanDistanceSq(festivalCenter[0], festivalCenter[1]); _log.debug("Distance: " + distToCenter + ", RegenMulti: " + (distToCenter * 2.5) / 50); return 1.0 - (distToCenter * 0.0005); // Maximum Decreased Regen of ~ -65%; } public static final double calcSiegeRegenModifer(L2PcInstance activeChar) { if (activeChar == null || activeChar.getClan() == null) return 0; Siege siege = SiegeManager.getSiege(activeChar.getPosition().getX(), activeChar.getPosition().getY(), activeChar.getPosition().getZ()); if (siege == null || !siege.getIsInProgress()) return 0; L2SiegeClan siegeClan = siege.getAttackerClan(activeChar.getClan().getClanId()); if (siegeClan == null || siegeClan.getFlag().isEmpty() || !Util.checkIfInRange(200, activeChar, siegeClan.getFlag().get(0), true)) return 0; return 1.5; // If all is true, then modifer will be 50% more } /** * @param attacker * The attacker, from where the blow comes from. * @param target * The victim of the blow. * @param skill * The skill used. * @param shld * True if victim was wearign a shield. * @param ss * True if ss were activated. * @return blow damage based on cAtk */ public static double calcBlowDamage(L2Character attacker, L2Character target, L2Skill skill, byte shld, boolean ss) { final boolean isPvP = (attacker instanceof L2Playable) && (target instanceof L2Playable); double power = skill.getPower(); double defence = target.getPDef(attacker); double damage = 0; damage += calcValakasAttribute(attacker, target, skill); if (ss) damage *= 2.; switch (shld) { case SHIELD_DEFENSE_SUCCEED: defence += target.getShldDef(); break; case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block return 1; } if (ss && skill.getSSBoost() > 0) power *= skill.getSSBoost(); damage += attacker.calcStat(Stats.CRITICAL_DAMAGE, (damage + power), target, skill); damage += attacker.calcStat(Stats.CRITICAL_DAMAGE_ADD, 0, target, skill) * 6.5; damage *= target.calcStat(Stats.CRIT_VULN, 1, target, skill); // get the vulnerability for the instance due to skills (buffs, passives, toggles, etc) damage = target.calcStat(Stats.DAGGER_WPN_VULN, damage, target, null); damage *= 70. / defence; // Random weapon damage damage *= attacker.getRandomDamageMultiplier(); // Dmg bonusses in PvP fight if (isPvP) damage *= attacker.calcStat(Stats.PVP_PHYS_SKILL_DMG, 1, null, null); return damage < 1 ? 1. : damage; } /** * Calculated damage caused by ATTACK of attacker on target, called separatly for each weapon, if dual-weapon is used. * * @param attacker * player or NPC that makes ATTACK * @param target * player or NPC, target of ATTACK * @param skill * skill used. * @param shld * target was using a shield or not. * @param crit * if the ATTACK have critical success * @param dual * if dual weapon is used * @param ss * if weapon item was charged by soulshot * @return damage points */ public static final double calcPhysDam(L2Character attacker, L2Character target, L2Skill skill, byte shld, boolean crit, boolean dual, boolean ss) { if (attacker instanceof L2PcInstance) { L2PcInstance pcInst = (L2PcInstance) attacker; if (pcInst.isGM() && !pcInst.getAccessLevel().canGiveDamage()) return 0; } final boolean isPvP = (attacker instanceof L2Playable) && (target instanceof L2Playable); double damage = attacker.getPAtk(target); double defence = target.getPDef(attacker); damage += calcValakasAttribute(attacker, target, skill); switch (shld) { case SHIELD_DEFENSE_SUCCEED: if (!PlayersConfig.ALT_GAME_SHIELD_BLOCKS) defence += target.getShldDef(); break; case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block return 1.; } if (ss) damage *= 2; if (skill != null) { double skillpower = skill.getPower(attacker, target); float ssboost = skill.getSSBoost(); if (ssboost <= 0) damage += skillpower; else if (ssboost > 0) { if (ss) { skillpower *= ssboost; damage += skillpower; } else damage += skillpower; } } // defence modifier depending of the attacker weapon L2Weapon weapon = attacker.getActiveWeaponItem(); Stats stat = null; if (weapon != null) { switch (weapon.getItemType()) { case BOW: stat = Stats.BOW_WPN_VULN; break; case BLUNT: stat = Stats.BLUNT_WPN_VULN; break; case BIGSWORD: stat = Stats.BIGSWORD_WPN_VULN; break; case BIGBLUNT: stat = Stats.BIGBLUNT_WPN_VULN; break; case DAGGER: stat = Stats.DAGGER_WPN_VULN; break; case DUAL: stat = Stats.DUAL_WPN_VULN; break; case DUALFIST: stat = Stats.DUALFIST_WPN_VULN; break; case ETC: stat = Stats.ETC_WPN_VULN; break; case FIST: stat = Stats.FIST_WPN_VULN; break; case POLE: stat = Stats.POLE_WPN_VULN; break; case SWORD: stat = Stats.SWORD_WPN_VULN; break; } } if (crit) { // Finally retail like formula damage = 2 * attacker.calcStat(Stats.CRITICAL_DAMAGE, 1, target, skill) * target.calcStat(Stats.CRIT_VULN, 1, target, null) * (70 * damage / defence); // Crit dmg add is almost useless in normal hits... damage += (attacker.calcStat(Stats.CRITICAL_DAMAGE_ADD, 0, target, skill) * 70 / defence); } else damage = 70 * damage / defence; if (stat != null) damage = target.calcStat(stat, damage, target, null); // Weapon random damage damage *= attacker.getRandomDamageMultiplier(); if (shld > 0 && PlayersConfig.ALT_GAME_SHIELD_BLOCKS) { damage -= target.getShldDef(); if (damage < 0) damage = 0; } if (target instanceof L2Npc) { double multiplier; switch (((L2Npc) target).getTemplate().getRace()) { case BEAST: multiplier = 1 + ((attacker.getPAtkMonsters(target) - target.getPDefMonsters(target)) / 100); damage *= multiplier; break; case ANIMAL: multiplier = 1 + ((attacker.getPAtkAnimals(target) - target.getPDefAnimals(target)) / 100); damage *= multiplier; break; case PLANT: multiplier = 1 + ((attacker.getPAtkPlants(target) - target.getPDefPlants(target)) / 100); damage *= multiplier; break; case DRAGON: multiplier = 1 + ((attacker.getPAtkDragons(target) - target.getPDefDragons(target)) / 100); damage *= multiplier; break; case BUG: multiplier = 1 + ((attacker.getPAtkInsects(target) - target.getPDefInsects(target)) / 100); damage *= multiplier; break; case GIANT: multiplier = 1 + ((attacker.getPAtkGiants(target) - target.getPDefGiants(target)) / 100); damage *= multiplier; break; case MAGICCREATURE: multiplier = 1 + ((attacker.getPAtkMagicCreatures(target) - target.getPDefMagicCreatures(target)) / 100); damage *= multiplier; break; default: // nothing break; } } if (damage > 0 && damage < 1) damage = 1; else if (damage < 0) damage = 0; // Dmg bonuses in PvP fight if (isPvP) { if (skill == null) damage *= attacker.calcStat(Stats.PVP_PHYSICAL_DMG, 1, null, null); else damage *= attacker.calcStat(Stats.PVP_PHYS_SKILL_DMG, 1, null, null); } return damage; } /** * Calculated damage caused by charges skills types. The special thing is about the multiplier (56 and not 70), and about the fixed amount of * damages * * @param attacker * player or NPC that makes ATTACK * @param target * player or NPC, target of ATTACK * @param skill * skill used. * @param shld * target was using a shield or not. * @param crit * if the ATTACK have critical success * @param dual * if dual weapon is used * @param ss * if weapon item was charged by soulshot * @return damage points */ public static final double calcChargeSkillsDam(L2Character attacker, L2Character target, L2Skill skill, byte shld, boolean crit, boolean dual, boolean ss) { if (attacker instanceof L2PcInstance) { L2PcInstance pcInst = (L2PcInstance) attacker; if (pcInst.isGM() && !pcInst.getAccessLevel().canGiveDamage()) return 0; } final boolean isPvP = (attacker instanceof L2Playable) && (target instanceof L2Playable); double damage = attacker.getPAtk(target); double defence = target.getPDef(attacker); damage += calcValakasAttribute(attacker, target, skill); switch (shld) { case SHIELD_DEFENSE_SUCCEED: if (!PlayersConfig.ALT_GAME_SHIELD_BLOCKS) defence += target.getShldDef(); break; case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block return 1.; } if (ss) damage *= 2; if (skill != null) { double skillpower = skill.getPower(attacker, target); float ssboost = skill.getSSBoost(); if (ssboost <= 0) damage += skillpower; else if (ssboost > 0) { if (ss) { skillpower *= ssboost; damage += skillpower; } else damage += skillpower; } } // defence modifier depending of the attacker weapon L2Weapon weapon = attacker.getActiveWeaponItem(); Stats stat = null; if (weapon != null) { switch (weapon.getItemType()) { case BOW: stat = Stats.BOW_WPN_VULN; break; case BLUNT: stat = Stats.BLUNT_WPN_VULN; break; case BIGSWORD: stat = Stats.BIGSWORD_WPN_VULN; break; case BIGBLUNT: stat = Stats.BIGBLUNT_WPN_VULN; break; case DAGGER: stat = Stats.DAGGER_WPN_VULN; break; case DUAL: stat = Stats.DUAL_WPN_VULN; break; case DUALFIST: stat = Stats.DUALFIST_WPN_VULN; break; case ETC: stat = Stats.ETC_WPN_VULN; break; case FIST: stat = Stats.FIST_WPN_VULN; break; case POLE: stat = Stats.POLE_WPN_VULN; break; case SWORD: stat = Stats.SWORD_WPN_VULN; break; } } if (crit) { // Finally retail like formula damage = 2 * attacker.calcStat(Stats.CRITICAL_DAMAGE, 1, target, skill) * target.calcStat(Stats.CRIT_VULN, 1, target, null) * (56 * damage / defence); // Crit dmg add is almost useless in normal hits... damage += (attacker.calcStat(Stats.CRITICAL_DAMAGE_ADD, 0, target, skill) * 56 / defence); } else damage = 56 * damage / defence; if (stat != null) damage = target.calcStat(stat, damage, target, null); // Weapon random damage damage *= attacker.getRandomDamageMultiplier(); if (shld > 0 && PlayersConfig.ALT_GAME_SHIELD_BLOCKS) { damage -= target.getShldDef(); if (damage < 0) damage = 0; } if (target instanceof L2Npc) { double multiplier; switch (((L2Npc) target).getTemplate().getRace()) { case BEAST: multiplier = 1 + ((attacker.getPAtkMonsters(target) - target.getPDefMonsters(target)) / 100); damage *= multiplier; break; case ANIMAL: multiplier = 1 + ((attacker.getPAtkAnimals(target) - target.getPDefAnimals(target)) / 100); damage *= multiplier; break; case PLANT: multiplier = 1 + ((attacker.getPAtkPlants(target) - target.getPDefPlants(target)) / 100); damage *= multiplier; break; case DRAGON: multiplier = 1 + ((attacker.getPAtkDragons(target) - target.getPDefDragons(target)) / 100); damage *= multiplier; break; case BUG: multiplier = 1 + ((attacker.getPAtkInsects(target) - target.getPDefInsects(target)) / 100); damage *= multiplier; break; case GIANT: multiplier = 1 + ((attacker.getPAtkGiants(target) - target.getPDefGiants(target)) / 100); damage *= multiplier; break; case MAGICCREATURE: multiplier = 1 + ((attacker.getPAtkMagicCreatures(target) - target.getPDefMagicCreatures(target)) / 100); damage *= multiplier; break; default: // nothing break; } } if (damage > 0 && damage < 1) damage = 1; else if (damage < 0) damage = 0; // Dmg bonuses in PvP fight if (isPvP) { if (skill == null) damage *= attacker.calcStat(Stats.PVP_PHYSICAL_DMG, 1, null, null); else damage *= attacker.calcStat(Stats.PVP_PHYS_SKILL_DMG, 1, null, null); } return damage; } public static final double calcMagicDam(L2Character attacker, L2Character target, L2Skill skill, byte shld, boolean ss, boolean bss, boolean mcrit) { if (attacker instanceof L2PcInstance) { L2PcInstance pcInst = (L2PcInstance) attacker; if (pcInst.isGM() && !pcInst.getAccessLevel().canGiveDamage()) return 0; } double mAtk = attacker.getMAtk(target, skill); double mDef = target.getMDef(attacker, skill); // AI SpiritShot if (attacker instanceof L2Npc) { if (((L2Npc) attacker)._spiritshotcharged) ss = true; else ss = false; ((L2Npc) attacker)._spiritshotcharged = false; } switch (shld) { case SHIELD_DEFENSE_SUCCEED: mDef += target.getShldDef(); break; case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block return 1; } if (bss) mAtk *= 4; else if (ss) mAtk *= 2; double damage = 91 * Math.sqrt(mAtk) / mDef * skill.getPower(attacker, target); // Failure calculation if (PlayersConfig.ALT_GAME_MAGICFAILURES && !calcMagicSuccess(attacker, target, skill)) { if (attacker instanceof L2PcInstance) { if (calcMagicSuccess(attacker, target, skill) && (target.getLevel() - attacker.getLevel()) <= 9) { if (skill.getSkillType() == L2SkillType.DRAIN) attacker.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.DRAIN_HALF_SUCCESFUL)); else attacker.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.ATTACK_FAILED)); damage /= 2; } else { attacker.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_RESISTED_YOUR_S2).addCharName(target).addSkillName(skill)); damage = 1; } } if (target instanceof L2PcInstance) { if (skill.getSkillType() == L2SkillType.DRAIN) target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.RESISTED_S1_DRAIN).addCharName(attacker)); else target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.RESISTED_S1_MAGIC).addCharName(attacker)); } } else if (mcrit) damage *= 4; // Pvp bonuses for dmg if (attacker instanceof L2Playable && target instanceof L2Playable) { if (skill.isMagic()) damage *= attacker.calcStat(Stats.PVP_MAGICAL_DMG, 1, null, null); else damage *= attacker.calcStat(Stats.PVP_PHYS_SKILL_DMG, 1, null, null); } damage *= calcElemental(attacker, target, skill); return damage; } public static final double calcMagicDam(L2CubicInstance attacker, L2Character target, L2Skill skill, boolean mcrit, byte shld) { // Current info include mAtk in the skill power. double mDef = target.getMDef(attacker.getOwner(), skill); switch (shld) { case SHIELD_DEFENSE_SUCCEED: mDef += target.getShldDef(); break; case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block return 1; } double damage = 91 / mDef * skill.getPower(); L2PcInstance owner = attacker.getOwner(); // Failure calculation if (PlayersConfig.ALT_GAME_MAGICFAILURES && !calcMagicSuccess(owner, target, skill)) { if (calcMagicSuccess(owner, target, skill) && (target.getLevel() - skill.getMagicLevel()) <= 9) { if (skill.getSkillType() == L2SkillType.DRAIN) owner.sendPacket(SystemMessageId.DRAIN_HALF_SUCCESFUL); else owner.sendPacket(SystemMessageId.ATTACK_FAILED); damage /= 2; } else { owner.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.S1_RESISTED_YOUR_S2).addCharName(target).addSkillName(skill)); damage = 1; } if (target instanceof L2PcInstance) { if (skill.getSkillType() == L2SkillType.DRAIN) target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.RESISTED_S1_DRAIN).addCharName(owner)); else target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.RESISTED_S1_MAGIC).addCharName(owner)); } } else if (mcrit) damage *= 4; damage *= calcElemental(owner, target, skill); return damage; } /** * @param rate * The value to make check on. * @return true in case of critical hit */ public static final boolean calcCrit(double rate) { return rate > Rnd.get(1000); } /** * Calcul value of blow success * * @param activeChar * The character delaing the blow. * @param target * The victim. * @param chance * The base chance of landing a blow. * @return true if successful, false otherwise */ public static final boolean calcBlow(L2Character activeChar, L2Character target, int chance) { return activeChar.calcStat(Stats.BLOW_RATE, chance * (1.0 + (activeChar.getDEX() - 20) / 100), target, null) > Rnd.get(100); } /** * Calcul value of lethal chance * * @param activeChar * The character delaing the blow. * @param target * The victim. * @param baseLethal * The base lethal chance of the skill. * @param magiclvl * @return */ public static final double calcLethal(L2Character activeChar, L2Character target, int baseLethal, int magiclvl) { double chance = 0; if (magiclvl > 0) { int delta = ((magiclvl + activeChar.getLevel()) / 2) - 1 - target.getLevel(); if (delta >= -3) chance = (baseLethal * ((double) activeChar.getLevel() / target.getLevel())); else if (delta < -3 && delta >= -9) chance = (-3) * (baseLethal / (delta)); else chance = baseLethal / 15; } else chance = (baseLethal * ((double) activeChar.getLevel() / target.getLevel())); return 10 * activeChar.calcStat(Stats.LETHAL_RATE, chance, target, null); } public static final boolean calcLethalHit(L2Character activeChar, L2Character target, L2Skill skill) { if (!target.isRaid() && !(target instanceof L2DoorInstance)) { // If one of following IDs is found, return false (Tyrannosaurus x 3, Headquarters) if (target instanceof L2Npc) { int npcId = ((L2Npc) target).getNpcId(); switch (npcId) { case 22215: case 22216: case 22217: case 35062: return false; } } // 2nd lethal effect activate (cp,hp to 1 or if target is npc then hp to 1) if (skill.getLethalChance2() > 0 && Rnd.get(1000) < calcLethal(activeChar, target, skill.getLethalChance2(), skill.getMagicLevel())) { if (target instanceof L2Npc) target.reduceCurrentHp(target.getCurrentHp() - 1, activeChar, skill); else if (target instanceof L2PcInstance) // If is a active player set his HP and CP to 1 { L2PcInstance player = (L2PcInstance) target; if (!player.isInvul()) { if (!(activeChar instanceof L2PcInstance && (((L2PcInstance) activeChar).isGM() && !((L2PcInstance) activeChar).getAccessLevel().canGiveDamage()))) { player.setCurrentHp(1); player.setCurrentCp(1); player.sendPacket(SystemMessageId.LETHAL_STRIKE_SUCCESSFUL); } } } activeChar.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.LETHAL_STRIKE)); } else if (skill.getLethalChance1() > 0 && Rnd.get(1000) < calcLethal(activeChar, target, skill.getLethalChance1(), skill.getMagicLevel())) { if (target instanceof L2PcInstance) { L2PcInstance player = (L2PcInstance) target; if (!player.isInvul()) { if (!(activeChar instanceof L2PcInstance && (((L2PcInstance) activeChar).isGM() && !((L2PcInstance) activeChar).getAccessLevel().canGiveDamage()))) player.setCurrentCp(1); // Set CP to 1 } } else if (target instanceof L2Npc) // If is a monster remove first damage and after 50% of current hp target.reduceCurrentHp(target.getCurrentHp() / 2, activeChar, skill); activeChar.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.LETHAL_STRIKE)); } else return false; } else return false; return true; } public static final boolean calcMCrit(int mRate) { if (MainConfig.DEVELOPER) _log.info("Current mCritRate: " + mRate + "/1000"); return mRate > Rnd.get(1000); } /** * Check if casting process is canceled due to hit. * * @param target * The target to make checks on. * @param dmg * The amount of dealt damages. */ public static final void calcCastBreak(L2Character target, double dmg) { // Don't go further for invul characters or raid bosses. if (target.isRaid() || target.isInvul()) return; // Break automatically the skill cast if under attack if (target instanceof L2PcInstance) { if (((L2PcInstance) target).getFusionSkill() != null) { target.breakCast(); return; } } double init = 0; // Initialization to 15% for magical skills if (target.isCastingNow() && (target.getLastSkillCast() != null && target.getLastSkillCast().isMagic())) init = 15; // Don't go further for ppl casting a physical skill if (init <= 0) return; // Chance to break is higher with higher dmg. init += Math.sqrt(13 * dmg); // Special treatement for monsters, else their casts are often broken. // Use a modifier according to AI type. if (target instanceof L2Attackable) { double mod = 0.1; // FIGHTER and ARCHER types use default mod, 0.1 switch (((L2Attackable) target).getAiType()) { case MAGE: case HEALER: mod = 0.3; break; case BALANCED: mod = 0.2; break; } init *= mod; } // Chance is affected by target MEN init -= (MENbonus[target.getMEN()] * 100 - 100); // Calculate all modifiers for ATTACK_CANCEL double rate = target.calcStat(Stats.ATTACK_CANCEL, init, null, null); // Adjust the rate to be between 1 and 99 if (rate > 99) rate = 99; else if (rate < 1) rate = 1; if (MainConfig.DEVELOPER) _log.info("calcCastBreak rate: " + (int) rate + "%"); if (Rnd.get(100) < rate) target.breakCast(); } /** * Calculate delay (in milliseconds) before next ATTACK. * * @param attacker * @param target * @param rate * @return delay in ms. */ public static final int calcPAtkSpd(L2Character attacker, L2Character target, double rate) { if (rate < 2) return 2700; return (int) (470000 / rate); } /** * Calculate delay (in milliseconds) for skills cast. * * @param attacker * @param skill * used to know if skill is magic or no. * @param skillTime * @return delay in ms. */ public static final int calcAtkSpd(L2Character attacker, L2Skill skill, double skillTime) { if (skill.isMagic()) return (int) (skillTime * 333 / attacker.getMAtkSpd()); return (int) (skillTime * 333 / attacker.getPAtkSpd()); } /** * Calculate the hit/miss chance. Take in consideration the attacker's accuracy, the target's evasion, and the difference of levels between * both. * * @param attacker * Take accuracy from the attacker. * @param target * Take evasion from the target. * @return true if hit missed (target evaded) */ public static boolean calcHitMiss(L2Character attacker, L2Character target) { int delta = attacker.getAccuracy() - target.getEvasionRate(attacker); int chance; if (delta >= 10) chance = 980; else { switch (delta) { case 9: chance = 975; break; case 8: chance = 970; break; case 7: chance = 965; break; case 6: chance = 960; break; case 5: chance = 955; break; case 4: chance = 945; break; case 3: chance = 935; break; case 2: chance = 925; break; case 1: chance = 915; break; case 0: chance = 905; break; case -1: chance = 890; break; case -2: chance = 875; break; case -3: chance = 860; break; case -4: chance = 845; break; case -5: chance = 830; break; case -6: chance = 815; break; case -7: chance = 800; break; case -8: chance = 785; break; case -9: chance = 770; break; case -10: chance = 755; break; case -11: chance = 735; break; case -12: chance = 715; break; case -13: chance = 695; break; case -14: chance = 675; break; case -15: chance = 655; break; case -16: chance = 625; break; case -17: chance = 595; break; case -18: chance = 565; break; case -19: chance = 535; break; case -20: chance = 505; break; case -21: chance = 455; break; case -22: chance = 405; break; case -23: chance = 355; break; case -24: chance = 305; break; default: chance = 275; } if (!attacker.isInFrontOfTarget()) { if (attacker.isBehindTarget()) chance *= 1.2; else // side chance *= 1.1; if (chance > 980) chance = 980; } } return chance < Rnd.get(1000); } /** * Test the shield use. * * @param attacker * The attacker. * @param target * The victim ; make check about his shield. * @param skill * The skill the attacker has used. * @param sendSysMsg * Send or no a system message. * @return 0 = shield defense doesn't succeed<br> * 1 = shield defense succeed<br> * 2 = perfect block */ public static byte calcShldUse(L2Character attacker, L2Character target, L2Skill skill, boolean sendSysMsg) { // Ignore shield skills types bypass the shield use. if (skill != null && skill.ignoreShield()) return 0; L2Item item = target.getSecondaryWeaponItem(); if (item == null || !(item instanceof L2Armor)) return 0; double shldRate = target.calcStat(Stats.SHIELD_RATE, 0, attacker, null) * Formulas.DEXbonus[target.getDEX()]; if (shldRate == 0.0) return 0; int degreeside = (int) target.calcStat(Stats.SHIELD_DEFENCE_ANGLE, 0, null, null) + 120; if (degreeside < 360 && (!target.isFacing(attacker, degreeside))) return 0; byte shldSuccess = SHIELD_DEFENSE_FAILED; // if attacker use bow and target wear shield, shield block rate is multiplied by 1.3 (30%) L2Weapon at_weapon = attacker.getActiveWeaponItem(); if (at_weapon != null && at_weapon.getItemType() == L2WeaponType.BOW) shldRate *= 1.3; if (shldRate > 0 && 100 - PlayersConfig.ALT_PERFECT_SHLD_BLOCK < Rnd.get(100)) shldSuccess = SHIELD_DEFENSE_PERFECT_BLOCK; else if (shldRate > Rnd.get(100)) shldSuccess = SHIELD_DEFENSE_SUCCEED; if (sendSysMsg && target instanceof L2PcInstance) { switch (shldSuccess) { case SHIELD_DEFENSE_SUCCEED: ((L2PcInstance) target).sendPacket(SystemMessageId.SHIELD_DEFENCE_SUCCESSFULL); break; case SHIELD_DEFENSE_PERFECT_BLOCK: ((L2PcInstance) target).sendPacket(SystemMessageId.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS); break; } } return shldSuccess; } public static byte calcShldUse(L2Character attacker, L2Character target, L2Skill skill) { return calcShldUse(attacker, target, skill, true); } public static byte calcShldUse(L2Character attacker, L2Character target) { return calcShldUse(attacker, target, null, true); } public static boolean calcMagicAffected(L2Character actor, L2Character target, L2Skill skill) { L2SkillType type = skill.getSkillType(); if (target.isRaid() && !calcRaidAffected(type)) return false; double defence = 0; if (skill.isActive() && skill.isOffensive()) defence = target.getMDef(actor, skill); double attack = 2 * actor.getMAtk(target, skill) * calcSkillVulnerability(actor, target, skill, type); double d = (attack - defence) / (attack + defence); d += 0.5 * Rnd.nextGaussian(); return d > 0; } public static double calcSkillVulnerability(L2Character attacker, L2Character target, L2Skill skill, L2SkillType type) { double multiplier = 1; // Get the elemental damages. if (skill.getElement() > 0) multiplier *= Math.sqrt(calcElemental(attacker, target, skill)); // Get the skillType to calculate its effect in function of base stats of the target. switch (type) { case BLEED: multiplier = target.calcStat(Stats.BLEED_VULN, multiplier, target, null); break; case POISON: multiplier = target.calcStat(Stats.POISON_VULN, multiplier, target, null); break; case STUN: multiplier = target.calcStat(Stats.STUN_VULN, multiplier, target, null); break; case PARALYZE: multiplier = target.calcStat(Stats.PARALYZE_VULN, multiplier, target, null); break; case ROOT: multiplier = target.calcStat(Stats.ROOT_VULN, multiplier, target, null); break; case SLEEP: multiplier = target.calcStat(Stats.SLEEP_VULN, multiplier, target, null); break; case MUTE: case FEAR: case BETRAY: case AGGDEBUFF: case AGGREDUCE_CHAR: case ERASE: case CONFUSION: multiplier = target.calcStat(Stats.DERANGEMENT_VULN, multiplier, target, null); break; case DEBUFF: case WEAKNESS: multiplier = target.calcStat(Stats.DEBUFF_VULN, multiplier, target, null); break; case CANCEL: multiplier = target.calcStat(Stats.CANCEL_VULN, multiplier, target, null); break; default: } // Return a multiplier (exemple with resist shock : 1 + (-0,4 stun vuln) = 0,6% return 1 + (multiplier / 100); } private static double calcSkillStatModifier(L2SkillType type, L2Character target) { double multiplier = 1; switch (type) { case STUN: case BLEED: case POISON: multiplier = 2 - sqrtCONbonus[target.getStat().getCON()]; break; case SLEEP: case DEBUFF: case WEAKNESS: case ERASE: case ROOT: case MUTE: case FEAR: case BETRAY: case CONFUSION: case AGGREDUCE_CHAR: case PARALYZE: multiplier = 2 - sqrtMENbonus[target.getStat().getMEN()]; break; } return Math.max(0, multiplier); } public static double getSTRBonus(L2Character activeChar) { return STRbonus[activeChar.getSTR()]; } private static double getLevelModifier(L2Character attacker, L2Character target, L2Skill skill) { if (skill.getLevelDepend() == 0) return 1; int delta = (skill.getMagicLevel() > 0 ? skill.getMagicLevel() : attacker.getLevel()) + skill.getLevelDepend() - target.getLevel(); return 1 + ((delta < 0 ? 0.04 : 0.02) * delta); } public static boolean calcEffectSuccess(L2Character attacker, L2Character target, EffectTemplate effect, L2Skill skill, byte shld, boolean ss, boolean sps, boolean bss) { if (shld == SHIELD_DEFENSE_PERFECT_BLOCK) // perfect block return false; final L2SkillType type = effect.effectType; final int value = (int) effect.effectPower; if (type == null) return Rnd.get(100) < value; else if (type.equals(L2SkillType.CANCEL)) // CANCEL type lands always return true; final double statModifier = calcSkillStatModifier(type, target); final double skillModifier = calcSkillVulnerability(attacker, target, skill, type); final double ssModifier = (bss ? 150 : (sps || ss ? 125 : 100)); final double lvlModifier = getLevelModifier(attacker, target, skill); // Calculate BaseRate. double rate = value * statModifier; double mAtkModifier = 0; // Add Matk/Mdef Bonus if (skill.isMagic()) { mAtkModifier = attacker.getMAtk(target, skill) / (2.0 * (target.getMDef(attacker, skill) + (shld == 1 ? target.getShldDef() : 0))); rate *= Math.pow(mAtkModifier, mAtkModifier < 1 ? 0.8 : 0.4); } // Add Bonus for Sps/SS if (ssModifier != 100) { if (rate > 10000 / (100 + ssModifier)) rate = 100 - (100 - rate) * 100 / ssModifier; else rate = rate * ssModifier / 100; } // Apply level modifier. rate *= lvlModifier; // Apply skill resist. rate *= skillModifier; rate = Math.max(1, Math.min(rate, 99)); if (MainConfig.DEVELOPER) { final StringBuilder stat = new StringBuilder(140); StringUtil.append(stat, "calcEffectSuccess(): Name:", skill.getName(), " eff.type:", type.toString(), " power:", String.valueOf(value), " statMod:", String.format("%1.2f", statModifier), " skillMod:", String.format("%1.2f", skillModifier), " mAtkMod:", String.format("%1.2f", mAtkModifier), " ssMod:", String.valueOf(ssModifier), " lvlMod:", String.format("%1.2f", lvlModifier), " total:", String.format("%1.2f", rate), "%"); final String result = stat.toString(); _log.info(result); } return (Rnd.get(100) < rate); } public static boolean calcSkillSuccess(L2Character attacker, L2Character target, L2Skill skill, byte shld, boolean ss, boolean sps, boolean bss) { final double baseChance = skill.getEffectPower(); return calcSkillSuccess(baseChance, attacker, target, skill, shld, ss, sps, bss); } public static boolean calcSkillSuccess(final double baseChance, L2Character attacker, L2Character target, L2Skill skill, byte shld, boolean ss, boolean sps, boolean bss) { if (shld == SHIELD_DEFENSE_PERFECT_BLOCK) // perfect block return false; final L2SkillType type = skill.getEffectType(); if (target.isRaid() && !calcRaidAffected(type)) return false; if (skill.ignoreResists()) return (Rnd.get(100) < baseChance); final double statModifier = calcSkillStatModifier(type, target); final double skillModifier = calcSkillVulnerability(attacker, target, skill, type); final double ssModifier = (bss ? 150 : (sps || ss ? 125 : 100)); final double lvlModifier = getLevelModifier(attacker, target, skill); // Calculate BaseRate. double rate = baseChance * statModifier; double mAtkModifier = 0; // Add Matk/Mdef Bonus if (skill.isMagic()) { mAtkModifier = attacker.getMAtk(target, skill) / (2.0 * (target.getMDef(attacker, skill) + (shld == 1 ? target.getShldDef() : 0))); rate *= Math.pow(mAtkModifier, mAtkModifier < 1 ? 0.8 : 0.4); } // Add Bonus for Sps/SS if (ssModifier != 100) { if (rate > 10000 / (100 + ssModifier)) rate = 100 - (100 - rate) * 100 / ssModifier; else rate = rate * ssModifier / 100; } // Apply level modifier. rate *= lvlModifier; // Apply skill resist. rate *= skillModifier; rate = Math.max(1, Math.min(rate, 99)); if (MainConfig.DEVELOPER) { final StringBuilder stat = new StringBuilder(140); StringUtil.append(stat, "calcSkillSuccess(): Name:", skill.getName(), " type:", skill.getSkillType().toString(), " power:", String.valueOf(baseChance), " statMod:", String.format("%1.2f", statModifier), " skillMod:", String.format("%1.2f", skillModifier), " mAtkMod:", String.format("%1.2f", mAtkModifier), " ssMod:", String.valueOf(ssModifier), " lvlMod:", String.format("%1.2f", lvlModifier), " total:", String.format("%1.2f", rate), "%"); final String result = stat.toString(); _log.info(result); } return (Rnd.get(100) < rate); } public static boolean calcCubicSkillSuccess(L2CubicInstance attacker, L2Character target, L2Skill skill, byte shld) { // if target reflect this skill then the effect will fail if (calcSkillReflect(target, skill) != SKILL_REFLECT_FAILED) return false; if (shld == SHIELD_DEFENSE_PERFECT_BLOCK) // perfect block return false; final L2SkillType type = skill.getEffectType(); if (target.isRaid() && !calcRaidAffected(type)) return false; final double baseChance = skill.getEffectPower(); if (skill.ignoreResists()) return Rnd.get(100) < baseChance; final double statModifier = calcSkillStatModifier(type, target); final double skillModifier = calcSkillVulnerability(attacker.getOwner(), target, skill, type); final double lvlModifier = getLevelModifier(attacker.getOwner(), target, skill); double rate = baseChance * statModifier * skillModifier; double mAtkModifier = 0; // Add Matk/Mdef Bonus if (skill.isMagic()) { mAtkModifier = attacker.getMAtk() / (2.0 * (target.getMDef(attacker.getOwner(), skill) + (shld == 1 ? target.getShldDef() : 0))); rate *= Math.pow(mAtkModifier, mAtkModifier < 1 ? 0.8 : 0.4) * 100 - 100; } rate *= lvlModifier; rate = Math.max(1, Math.min(rate, 99)); if (MainConfig.DEVELOPER) { final StringBuilder stat = new StringBuilder(140); StringUtil.append(stat, "calcCubicSkillSuccess(): Name:", skill.getName(), " type:", skill.getSkillType().toString(), " power:", String.valueOf(baseChance), " statMod:", String.format("%1.2f", statModifier), " skillMod:", String.format("%1.2f", skillModifier), " mAtkMod:", String.format("%1.2f", mAtkModifier), " lvlMod:", String.format("%1.2f", lvlModifier), " total:", String.format("%1.2f", rate), "%"); final String result = stat.toString(); _log.info(result); } return (Rnd.get(100) < rate); } public static boolean calcMagicSuccess(L2Character attacker, L2Character target, L2Skill skill) { int lvlDifference = target.getLevel() - ((skill.getMagicLevel() > 0 ? skill.getMagicLevel() : attacker.getLevel()) + skill.getLevelDepend()); double rate = 100; if (lvlDifference > 0) rate = (Math.pow(1.3, lvlDifference)) * 100; if (MainConfig.DEVELOPER) { final StringBuilder stat = new StringBuilder(80); StringUtil.append(stat, "calcMagicSuccess(): Name:", skill.getName(), " lvlDiff:", String.valueOf(lvlDifference), " fail:", String.format("%1.2f", rate / 100), "%"); final String result = stat.toString(); _log.info(result); } rate = Math.min(rate, 9900); return (Rnd.get(10000) > rate); } public static boolean calculateUnlockChance(L2Skill skill) { int level = skill.getLevel(); int chance = 0; switch (level) { case 1: chance = 30; break; case 2: chance = 50; break; case 3: chance = 75; break; case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: chance = 100; break; } if (Rnd.get(100) > chance) return false; return true; } public static double calcManaDam(L2Character attacker, L2Character target, L2Skill skill, boolean ss, boolean bss) { double mAtk = attacker.getMAtk(target, skill); double mDef = target.getMDef(attacker, skill); double mp = target.getMaxMp(); if (bss) mAtk *= 4; else if (ss) mAtk *= 2; double damage = (Math.sqrt(mAtk) * skill.getPower(attacker, target) * (mp / 97)) / mDef; damage *= calcSkillVulnerability(attacker, target, skill, skill.getSkillType()); return damage; } public static double calculateSkillResurrectRestorePercent(double baseRestorePercent, L2Character caster) { if (baseRestorePercent == 0 || baseRestorePercent == 100) return baseRestorePercent; double restorePercent = baseRestorePercent * Formulas.WITbonus[caster.getWIT()]; if (restorePercent - baseRestorePercent > 20.0) restorePercent += 20.0; restorePercent = Math.max(restorePercent, baseRestorePercent); restorePercent = Math.min(restorePercent, 90.0); return restorePercent; } public static boolean calcPhysicalSkillEvasion(L2Character target, L2Skill skill) { if (skill.isMagic()) return false; return Rnd.get(100) < target.calcStat(Stats.P_SKILL_EVASION, 0, null, skill); } public static boolean calcSkillMastery(L2Character actor, L2Skill sk) { // Pointless check for L2Character other than players, as initial value will stay 0. if (!(actor instanceof L2PcInstance)) return false; if (sk.getSkillType() == L2SkillType.FISHING) return false; double val = actor.getStat().calcStat(Stats.SKILL_MASTERY, 0, null, null); if (((L2PcInstance) actor).isMageClass()) val *= INTbonus[actor.getINT()]; else val *= STRbonus[actor.getSTR()]; return Rnd.get(100) < val; } public static double calcValakasAttribute(L2Character attacker, L2Character target, L2Skill skill) { double calcPower = 0; double calcDefen = 0; if (skill != null && skill.getAttributeName().contains("valakas")) { calcPower = attacker.calcStat(Stats.VALAKAS, calcPower, target, skill); calcDefen = target.calcStat(Stats.VALAKAS_RES, calcDefen, target, skill); } else { calcPower = attacker.calcStat(Stats.VALAKAS, calcPower, target, skill); if (calcPower > 0) { calcPower = attacker.calcStat(Stats.VALAKAS, calcPower, target, skill); calcDefen = target.calcStat(Stats.VALAKAS_RES, calcDefen, target, skill); } } return calcPower - calcDefen; } public static double calcElemental(L2Character attacker, L2Character target, L2Skill skill) { if (skill != null) { final byte element = skill.getElement(); if (element >= 0) { // power is basically put to 20 int calcPower = 20; int calcDefen = target.getDefenseElementValue(element); /* * if (attacker.getAttackElement() == element) FIXME implementation elemental to a skill. calcPower += * attacker.getAttackElementValue(element); */ int calcTotal = calcPower - calcDefen; if (calcTotal > 0) { if (calcTotal < 75) return 1 + calcTotal * 0.0052; else if (calcTotal < 150) return 1.4; else if (calcTotal < 290) return 1.7; else if (calcTotal < 300) return 1.8; else return 2.0; } } }/* * else { final byte element = attacker.getAttackElement(); if (element >= 0) { int calcPower = attacker.getAttackElementValue(element); * int calcDefen = target.getDefenseElementValue(element); return 1 + L2Math.limit(-20, calcPower - calcDefen, 100) * 0.007; } } */ return 1; } /** * Calculate skill reflection according these three possibilities: <li>Reflect failed</li> <li>Mormal reflect (just effects). <U>Only * possible for skilltypes: BUFF, REFLECT, HEAL_PERCENT, MANAHEAL_PERCENT, HOT, CPHOT, MPHOT</U></li> <li>vengEance reflect (100% damage * reflected but damage is also dealt to actor). <U>This is only possible for skills with skilltype PDAM, BLOW, CHARGEDAM, MDAM or * DEATHLINK</U></li> * * @param target * @param skill * @return SKILL_REFLECTED_FAILED, SKILL_REFLECT_SUCCEED or SKILL_REFLECT_VENGEANCE */ public static byte calcSkillReflect(L2Character target, L2Skill skill) { /* * Neither some special skills (like hero debuffs...) or those skills ignoring resistances can be reflected */ if (skill.ignoreResists() || !skill.canBeReflected()) return SKILL_REFLECT_FAILED; // only magic and melee skills can be reflected if (!skill.isMagic() && (skill.getCastRange() == -1 || skill.getCastRange() > MELEE_ATTACK_RANGE)) return SKILL_REFLECT_FAILED; byte reflect = SKILL_REFLECT_FAILED; // check for non-reflected skilltypes, need additional retail check switch (skill.getSkillType()) { case BUFF: case REFLECT: case HEAL_PERCENT: case MANAHEAL_PERCENT: case HOT: case CPHOT: case MPHOT: case UNDEAD_DEFENSE: case AGGDEBUFF: case CONT: return SKILL_REFLECT_FAILED; // these skill types can deal damage case PDAM: case BLOW: case MDAM: case DEATHLINK: case CHARGEDAM: final Stats stat = skill.isMagic() ? Stats.VENGEANCE_SKILL_MAGIC_DAMAGE : Stats.VENGEANCE_SKILL_PHYSICAL_DAMAGE; final double venganceChance = target.getStat().calcStat(stat, 0, target, skill); if (venganceChance > Rnd.get(100)) reflect |= SKILL_REFLECT_VENGEANCE; break; } final double reflectChance = target.calcStat(skill.isMagic() ? Stats.REFLECT_SKILL_MAGIC : Stats.REFLECT_SKILL_PHYSIC, 0, null, skill); if (Rnd.get(100) < reflectChance) reflect |= SKILL_REFLECT_SUCCEED; return reflect; } /** * Calculate damage caused by falling * * @param cha * @param fallHeight * @return damage */ public static double calcFallDam(L2Character cha, int fallHeight) { if (!MainConfig.ENABLE_FALLING_DAMAGE || fallHeight < 0) return 0; final double damage = cha.calcStat(Stats.FALL, fallHeight * cha.getMaxHp() / 1000, null, null); return damage; } public static int calcNpcHpBonus(int CON, int baseHpMax) { try { baseHpMax = (int) (baseHpMax * CONbonus[CON]); return baseHpMax; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return baseHpMax; } } public static int calcNpcMpBonus(int MEN, int baseMpMax) { try { baseMpMax = (int) (baseMpMax * MENbonus[MEN]); return baseMpMax; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return baseMpMax; } } public static double calcNpcMdefBonus(int MEN, int baseMdef, int level) { try { return (0.00905984 * level + 0.811133) * baseMdef * MENbonus[MEN]; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return baseMdef; } } public static double calcNpcPdefBonus(int basePdef, int level) { try { return (0.0100095 * level + 0.890021) * basePdef; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return basePdef; } } public static final double calcNpcMatkBonus(int INT, int baseMatk, int level) { try { return (0.0100095 * level + 0.890021) * baseMatk * INTbonus[INT]; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return baseMatk; } } public static final double calcNpcPatkBonus(int STR, int basePatk, int level) { try { return (0.00986325 * level + 0.885473) * basePatk * STRbonus[STR]; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return basePatk; } } public static final int calcNpcPatkSpdBonus(int DEX, int basePatkSpd) { try { basePatkSpd = (int) (basePatkSpd * DEXbonus[DEX]); return basePatkSpd; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return basePatkSpd; } } public static final int calcNpcMoveBonus(int DEX, int baseMoveSpd) { try { baseMoveSpd = (int) (baseMoveSpd * DEXbonus[DEX]); return baseMoveSpd; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return baseMoveSpd; } } public static final int calcNpcMatkSpdBonus(int WIT, int baseMatkSpd) { try { baseMatkSpd = (int) (baseMatkSpd * WITbonus[WIT]); return baseMatkSpd; } catch (Exception e) { _log.warn("Formulas: Error in NPC Data!", e); return baseMatkSpd; } } public static boolean calcRaidAffected(L2SkillType type) { switch (type) { case MANADAM: case MDOT: return true; case CONFUSION: case ROOT: case STUN: case MUTE: case FEAR: case DEBUFF: case PARALYZE: case SLEEP: case AGGDEBUFF: case AGGREDUCE_CHAR: if (Rnd.get(1000) == 1) return true; } return false; } }