Баг со скорость атаки Лукарей\Арб (линдвиор)

Caper111

Путник
Пользователь
Сообщения
18
Розыгрыши
0
Репутация
0
Реакции
0
Баллы
0
Очень странное поведение персонажей с кастом 1500(в статистике перса)+ они бьют очень медленно,как будто что-то урезает их каст. (только Луки и Арбы,остальные норм) В конфиге смотрел, ограничение до 1999 скорость атаки, кто знает как решить? где копать? может в ядре? может в датапаке?
 
Скорость, с которой они бьют, не зависит от каста.
 
Очень странное поведение персонажей с кастом 1500(в статистике перса)+ они бьют очень медленно,как будто что-то урезает их каст. (только Луки и Арбы,остальные норм) В конфиге смотрел, ограничение до 1999 скорость атаки, кто знает как решить? где копать? может в ядре? может в датапаке?
если сборка овероподобная то в альтсеттинге конфиг есть
 
если сборка овероподобная то в альтсеттинге конфиг есть
Можно для "тупых" что за альтсеттинг? сборка Годов

Скорость, с которой они бьют, не зависит от каста.
а от чего тогда зависит ? разве не от скорости физ атаки?
 
Последнее редактирование модератором:
Можно для "тупых" что за альтсеттинг? сборка Годов


а от чего тогда зависит ? разве не от скорости физ атаки?
автоатака у луков зависит от Atk Speed
атака со скилов от Cast Speed
всегда так было

по поводу твоей проблемы, то причин может быть много, от конфига
# Использовать базовую скорость как модификатор скорости стрельбы, выключать не рекомендуется (большая халява)
AltUseBowReuseModifier = True
до формул в ядре.
 
а от чего тогда зависит ? разве не от скорости физ атаки?
Скорость атаки и скорость каста это разные параметры. При использовании оружия типа BOW или CROSSBOW также используется параметр atkReuse
 
Скорость атаки и скорость каста это разные параметры. При использовании оружия типа BOW или CROSSBOW также используется параметр atkReuse
я имею ввиду скорость атаки луком\арбалетом а не каст. С кастом все в порядке

автоатака у луков зависит от Atk Speed
атака со скилов от Cast Speed
всегда так было

по поводу твоей проблемы, то причин может быть много, от конфига
# Использовать базовую скорость как модификатор скорости стрельбы, выключать не рекомендуется (большая халява)
AltUseBowReuseModifier = True
до формул в ядре.
Спасибо, думаю
автоатака у луков зависит от Atk Speed
атака со скилов от Cast Speed
всегда так было

по поводу твоей проблемы, то причин может быть много, от конфига
# Использовать базовую скорость как модификатор скорости стрельбы, выключать не рекомендуется (большая халява)
AltUseBowReuseModifier = True
до формул в ядре.
И так, проверил конфиги, такого нет. Если проблема в ядре,что это может быть?
 
Последнее редактирование модератором:
И так, проверил конфиги, такого нет. Если проблема в ядре,что это может быть?
Ну наверное что-то типа этого:
Код:
package org.mmocore.gameserver.stats;

import org.mmocore.commons.util.Rnd;
import org.mmocore.gameserver.Config;
import org.mmocore.gameserver.model.Creature;
import org.mmocore.gameserver.model.Effect;
import org.mmocore.gameserver.model.Player;
import org.mmocore.gameserver.model.Skill;
import org.mmocore.gameserver.model.Skill.SkillMagicType;
import org.mmocore.gameserver.model.base.BaseStats;
import org.mmocore.gameserver.model.base.Element;
import org.mmocore.gameserver.model.base.SkillTrait;
import org.mmocore.gameserver.model.instances.ReflectionBossInstance;
import org.mmocore.gameserver.model.items.ItemInstance;
import org.mmocore.gameserver.network.l2.components.SystemMsg;
import org.mmocore.gameserver.network.l2.s2c.SystemMessage;
import org.mmocore.gameserver.skills.EffectType;
import org.mmocore.gameserver.skills.SkillEntry;
import org.mmocore.gameserver.skills.effects.EffectTemplate;
import org.mmocore.gameserver.templates.item.WeaponTemplate;
import org.mmocore.gameserver.utils.PositionUtils;

public class Formulas
{
    private static final boolean[] DEBUG_DISABLED = { false, false, false};

    public static double calcHpRegen(Creature cha)
    {
        double init;
        if(cha.isPlayer())
            init = (cha.getLevel() <= 10 ? 1.5 + cha.getLevel() / 20. : 1.4 + cha.getLevel() / 10.) * cha.getLevelMod();
        else
            init = cha.getTemplate().baseHpReg;

        if(cha.isPlayable())
        {
            init *= BaseStats.CON.calcBonus(cha);
            if(cha.isSummon())
                init *= 2;
        }

        return cha.calcStat(Stats.REGENERATE_HP_RATE, init, null, null);
    }

    public static double calcMpRegen(Creature cha)
    {
        double init;
        if(cha.isPlayer())
            init = (.87 + cha.getLevel() * .03) * cha.getLevelMod();
        else
            init = cha.getTemplate().baseMpReg;

        if(cha.isPlayable())
        {
            init *= BaseStats.MEN.calcBonus(cha);
            if(cha.isSummon())
                init *= 2;
        }

        return cha.calcStat(Stats.REGENERATE_MP_RATE, init, null, null);
    }

    public static double calcCpRegen(Creature cha)
    {
        double init = (1.5 + cha.getLevel() / 10) * cha.getLevelMod() * BaseStats.CON.calcBonus(cha);
        return cha.calcStat(Stats.REGENERATE_CP_RATE, init, null, null);
    }

    public static class AttackInfo
    {
        public double damage = 0;
        public double defence = 0;
        public double crit_static = 0;
        public double death_rcpt = 0;
        public double lethal1 = 0;
        public double lethal2 = 0;
        public double lethal_dmg = 0;
        public boolean crit = false;
        public boolean shld = false;
        public boolean lethal = false;
        public boolean miss = false;
    }

    /**
     * Для простых ударов
     * patk = patk
     * При крите простым ударом:
     * patk = patk * (1 + crit_damage_rcpt) * crit_damage_mod + crit_damage_static
     * Для blow скиллов
     * TODO
     * Для скилловых критов, повреждения просто удваиваются, бафы не влияют (кроме blow, для них выше)
     * patk = (1 + crit_damage_rcpt) * (patk + skill_power)
     * Для обычных атак
     * damage = patk * ss_bonus * 70 / pdef
     */
    public static AttackInfo calcPhysDam(Creature attacker, Creature target, SkillEntry skill, boolean dual, boolean blow, boolean ss, boolean onCrit)
    {
        AttackInfo info = new AttackInfo();

        info.damage = attacker.getPAtk(target);
        info.defence = target.getPDef(attacker);
        info.crit_static = attacker.calcStat(Stats.CRITICAL_DAMAGE_STATIC, target, skill);
        info.death_rcpt = 0.01 * target.calcStat(Stats.DEATH_VULNERABILITY, attacker, skill);
        info.lethal1 = skill == null ? 0 : skill.getTemplate().getLethal1() * info.death_rcpt;
        info.lethal2 = skill == null ? 0 : skill.getTemplate().getLethal2() * info.death_rcpt;
        info.crit = Rnd.chance(calcCrit(attacker, target, skill, blow));
        info.shld = (skill == null || !skill.getTemplate().getShieldIgnore()) && Formulas.calcShldUse(attacker, target);
        info.lethal = false;
        info.miss = false;
        boolean isPvP = attacker.isPlayable() && target.isPlayable();

        if(info.shld)
            info.defence += target.getShldDef();

        info.defence = Math.max(info.defence, 1);

        if(skill != null)
        {
            final SkillTrait trait = skill.getTemplate().getTraitType();
            if (trait != null)
            {
                final Env env = new Env(attacker, target, skill);
                double traitMul = 1. + (trait.calcProf(env) - trait.calcVuln(env)) / 100.;
                if (traitMul == Double.NEGATIVE_INFINITY) // invul
                {
                    info.damage = 0;
                    return info;
                }
                /*else if (traitMul > 2.) // DS: нужны тесты
                    traitMul = 2.;
                else if (traitMul < 0.05)
                    traitMul = 0.05;

                power *= traitMul;*/
            }

            if(!blow && !target.isLethalImmune() && target.getLevel() - skill.getTemplate().getMagicLevel() <= 5) // считаем леталы для не blow скиллов
                if(info.lethal1 > 0 && Rnd.chance(info.lethal1))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentCp();
                        target.sendPacket(SystemMsg.YOUR_CP_WAS_DRAINED_BECAUSE_YOU_WERE_HIT_WITH_A_CP_SIPHON_SKILL);
                    }
                    else
                        info.lethal_dmg = target.getCurrentHp() / 2;
                    attacker.sendPacket(SystemMsg.CP_SIPHON);
                }
                else if(info.lethal2 > 0 && Rnd.chance(info.lethal2))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentHp() + target.getCurrentCp() - 1;
                        target.sendPacket(SystemMsg.LETHAL_STRIKE);
                    }
                    else
                        info.lethal_dmg = target.getCurrentHp() - 1;
                    attacker.sendPacket(SystemMsg.YOUR_LETHAL_STRIKE_WAS_SUCCESSFUL);
                }

            // если скилл не имеет своей силы дальше идти бесполезно, можно сразу вернуть дамаг от летала
            if(skill.getTemplate().getPower(target) == 0)
            {
                info.damage = 0; // обычного дамага в этом случае не наносится
                return info;
            }

            if(blow && !skill.getTemplate().isBehind() && ss) // Для обычных blow не влияет на power
                info.damage *= 2.04;

            // Для зарядок влияет на суммарный бонус
            if(skill.getTemplate().isChargeBoost())
                info.damage = attacker.calcStat(Stats.SKILL_POWER, info.damage + skill.getTemplate().getPower(target), null, null);
            else
                info.damage += attacker.calcStat(Stats.SKILL_POWER, skill.getTemplate().getPower(target), null, null);

            if(blow && skill.getTemplate().isBehind() && ss) // Для backstab влияет на power, но меньше множитель
                info.damage *= 1.5;

            //Заряжаемые скилы имеют постоянный урон
            if(!skill.getTemplate().isChargeBoost())
                info.damage *= 1 + (Rnd.get() * attacker.getRandomDamage() * 2 - attacker.getRandomDamage()) / 100;

            if(blow)
            {
                // DS: Focus Death и Focus Power умножаются на 0.5 в датапаке
                info.damage *= 0.01 * attacker.calcStat(Stats.CRITICAL_DAMAGE, target, skill);
                info.damage = target.calcStat(Stats.CRIT_DAMAGE_RECEPTIVE, info.damage, attacker, skill);
                info.damage += 6.1 * info.crit_static;
            }

            if(skill.getTemplate().isChargeBoost())
                info.damage *= 0.8 + 0.2 * (attacker.getIncreasedForce() + Math.max(skill.getTemplate().getNumCharges(), 0));
            else if(skill.getTemplate().isSoulBoost())
                info.damage *= 1.0 + 0.06 * Math.min(attacker.getConsumedSouls(), 5);

            // Gracia Physical Skill Damage Bonus
            info.damage *= 1.10113;

            if(info.crit)
                info.damage *= 2.;
        }
        else
        {
            info.damage *= 1 + (Rnd.get() * attacker.getRandomDamage() * 2 - attacker.getRandomDamage()) / 100;

            if(dual)
                info.damage /= 2.;

            if(info.crit)
            {
                info.damage *= 0.01 * attacker.calcStat(Stats.CRITICAL_DAMAGE, target, skill);
                info.damage = 2 * target.calcStat(Stats.CRIT_DAMAGE_RECEPTIVE, info.damage, attacker, skill);
                info.damage += info.crit_static;
            }
        }

        if(info.crit)
        {
            // шанс абсорбации души (без анимации) при крите, если Soul Mastery 4го уровня или более
            int chance = attacker.getSkillLevel(Skill.SKILL_SOUL_MASTERY);
            if(chance > 0)
            {
                if(chance >= 21)
                    chance = 30;
                else if(chance >= 15)
                    chance = 25;
                else if(chance >= 9)
                    chance = 20;
                else if(chance >= 4)
                    chance = 15;
                if(Rnd.chance(chance))
                    attacker.setConsumedSouls(attacker.getConsumedSouls() + 1, null);
            }
        }

        // у зарядок нет бонусов от положения цели
        if (skill == null || !skill.getTemplate().isChargeBoost())
            switch(PositionUtils.getDirectionTo(target, attacker))
            {
                case BEHIND:
                    info.damage *= 1.2;
                    break;
                case SIDE:
                    info.damage *= 1.1;
                    break;
            }

        if(ss && !blow)
            info.damage *= 2.0;

        info.damage *= 70. / info.defence;
        info.damage = attacker.calcStat(Stats.PHYSICAL_DAMAGE, info.damage, target, skill);

        if(info.shld && Rnd.chance(5))
            info.damage = 1;

        if(isPvP)
        {
            if(skill == null)
            {
                info.damage *= attacker.calcStat(Stats.PVP_PHYS_DMG_BONUS, 1, null, null);
                info.damage /= target.calcStat(Stats.PVP_PHYS_DEFENCE_BONUS, 1, null, null);
            }
            else
            {
                info.damage *= attacker.calcStat(Stats.PVP_PHYS_SKILL_DMG_BONUS, 1, null, null);
                info.damage /= target.calcStat(Stats.PVP_PHYS_SKILL_DEFENCE_BONUS, 1, null, null);
            }
        }

        // Тут проверяем только если skill != null, т.к. L2Character.onHitTimer не обсчитывает дамаг.
        if(skill != null)
        {
            if(info.shld)
                if(info.damage == 1)
                    target.sendPacket(SystemMsg.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
                else
                    target.sendPacket(SystemMsg.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);

            // Уворот от физ скилов уводит атаку в 0
            if(info.damage > 1 && !skill.getTemplate().hasNotSelfEffects() && Rnd.chance(target.calcStat(Stats.PSKILL_EVASION, 0, attacker, skill)))
            {
                attacker.sendPacket(new SystemMessage(SystemMsg.C1_DODGES_THE_ATTACK).addName(target));
                target.sendPacket(new SystemMessage(SystemMsg.YOU_HAVE_AVOIDED_C1S_ATTACK).addName(attacker));
                info.damage = 0;
            }

            if(info.damage > 1 && skill.getTemplate().isDeathlink())
                info.damage *= 1.8 * (1.0 - attacker.getCurrentHpRatio());

            if(onCrit && !calcBlow(attacker, target, skill))
            {
                info.miss = true;
                info.damage = 0;
                //attacker.sendPacket(new SystemMessage(SystemMsg.C1S_ATTACK_WENT_ASTRAY).addName(attacker));
            }

            if(blow && target.getLevel() - skill.getTemplate().getMagicLevel() <= 5)
                if(info.lethal1 > 0 && Rnd.chance(info.lethal1))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentCp();
                        target.sendPacket(SystemMsg.YOUR_CP_WAS_DRAINED_BECAUSE_YOU_WERE_HIT_WITH_A_CP_SIPHON_SKILL);
                    }
                    else if(target.isLethalImmune())
                        info.damage *= 2;
                    else
                        info.lethal_dmg = target.getCurrentHp() / 2;
                    attacker.sendPacket(SystemMsg.CP_SIPHON);
                }
                else if(info.lethal2 > 0 && Rnd.chance(info.lethal2))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentHp() + target.getCurrentCp() - 1;
                        target.sendPacket(SystemMsg.LETHAL_STRIKE);
                    }
                    else if(target.isLethalImmune())
                        info.damage *= 3;
                    else
                        info.lethal_dmg = target.getCurrentHp() - 1;
                    attacker.sendPacket(SystemMsg.YOUR_LETHAL_STRIKE_WAS_SUCCESSFUL);
                }

            if(info.damage > 0)
                attacker.displayGiveDamageMessage(target, (int) info.damage, info.crit || blow, false, false, false);

            calcStunBreak(target, info.crit);

            if(calcCastBreak(target, info.crit))
                target.abortCast(false, true);
        }

        return info;
    }

    public static double calcMagicDam(Creature attacker, Creature target, SkillEntry skill, int sps, boolean toMp)
    {
        boolean isPvP = attacker.isPlayable() && target.isPlayable();
        // Параметр ShieldIgnore для магических скиллов инвертирован
        boolean shield = skill.getTemplate().getShieldIgnore() && calcShldUse(attacker, target);

        double mAtk = attacker.getMAtk(target, skill);

        if(sps == 2)
            mAtk *= 4;
        else if(sps == 1)
            mAtk *= 2;

        double mdef = target.getMDef(null, skill);

        if(shield)
            mdef += target.getShldDef();
        if(mdef == 0)
            mdef = 1;

        double power = skill.getTemplate().getPower(target);

        boolean gradePenalty = attacker.isPlayer() && ((Player)attacker).getWeaponsExpertisePenalty() > 0;

        final SkillTrait trait = skill.getTemplate().getTraitType();
        if (trait != null)
        {
            final Env env = new Env(attacker, target, skill);
            double traitMul = 1. + (trait.calcProf(env) - trait.calcVuln(env)) / 100.;
            if (traitMul == Double.NEGATIVE_INFINITY) // invul
                return 0;
            else if (traitMul > 2.)
                traitMul = 2.;
            else if (traitMul < 0.05)
                traitMul = 0.05;

            power *= traitMul;
        }

        double lethalDamage = 0;

        if (target.getLevel() - skill.getTemplate().getMagicLevel() <= 5 && !gradePenalty)
        {
            if(skill.getTemplate().getLethal1() > 0 && Rnd.chance(skill.getTemplate().getLethal1()))
            {
                if(target.isPlayer())
                {
                    lethalDamage = target.getCurrentCp();
                    target.sendPacket(SystemMsg.YOUR_CP_WAS_DRAINED_BECAUSE_YOU_WERE_HIT_WITH_A_CP_SIPHON_SKILL);
                }
                else if(!target.isLethalImmune())
                    lethalDamage = target.getCurrentHp() / 2;
                else
                    power *= 2;
                attacker.sendPacket(SystemMsg.CP_SIPHON);
            }
            else if(skill.getTemplate().getLethal2() > 0 && Rnd.chance(skill.getTemplate().getLethal2()))
            {
                if(target.isPlayer())
                {
                    lethalDamage = target.getCurrentHp() + target.getCurrentCp() - 1;
                    target.sendPacket(SystemMsg.LETHAL_STRIKE);
                }
                else if(!target.isLethalImmune())
                    lethalDamage = target.getCurrentHp() - 1;
                else
                    power *= 3;
                attacker.sendPacket(SystemMsg.YOUR_LETHAL_STRIKE_WAS_SUCCESSFUL);
            }
        }

        if(power == 0)
        {
            if(lethalDamage > 0)
                attacker.displayGiveDamageMessage(target, (int) lethalDamage, false, false, false, false);
            return lethalDamage;
        }

        if(skill.getTemplate().isSoulBoost())
            power *= 1.0 + 0.0225 * Math.min(attacker.getConsumedSouls(), 5);

        boolean crit = false;
        double damage = power * Math.sqrt(mAtk) / mdef;
        if (toMp)
        {
            if (isPvP)
                damage *= target.getMaxMp() / 97.;
            else
            {
                damage *= 91.;
                damage = Math.max(1, damage / 2.);
            }
        }
        else
            damage *= 91.;

        if (skill.getTemplate().getMatak() == 0) // у кубиков нет рандомдамага и критов
        {
            damage *= 1 + (Rnd.get() * attacker.getRandomDamage() * 2 - attacker.getRandomDamage()) / 100;
            crit = calcMCrit(attacker.getMagicCriticalRate(target, skill));           
        }

        if(crit)
            damage *= attacker.calcStat(Stats.MCRITICAL_DAMAGE, attacker.isPlayable() && target.isPlayable() ? 2.5 : 3., target, skill);

        damage = attacker.calcStat(Stats.MAGIC_DAMAGE, damage, target, skill);

        if(shield)
        {
            if(Rnd.chance(5))
            {
                damage = 0;
                target.sendPacket(SystemMsg.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
                attacker.sendPacket(new SystemMessage(SystemMsg.C1_RESISTED_C2S_MAGIC).addName(target).addName(attacker));
            }
            else
            {
                target.sendPacket(SystemMsg.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);
                attacker.sendPacket(new SystemMessage(SystemMsg.YOUR_OPPONENT_HAS_RESISTANCE_TO_MAGIC_THE_DAMAGE_WAS_DECREASED));
            }
        }

        int levelDiff = target.getLevel() - attacker.getLevel(); // C Gracia Epilogue уровень маг. атак считается только по уроню атакующего

        if (damage > 1)
        {
            if (skill.getTemplate().isDeathlink())
                damage *= 1.8 * (1.0 - attacker.getCurrentHpRatio());

            if(skill.getTemplate().isBasedOnTargetDebuff())
            {
                int effectCount = 0;
                for (Effect e : target.getEffectList().getAllFirstEffects())
                    if (!e.getSkill().getTemplate().isToggle())
                        effectCount++;

                damage *= 0.3 + 0.0875 * effectCount;
            }
        }

        damage += lethalDamage;

        if(isPvP && damage > 1)
        {
            damage *= attacker.calcStat(Stats.PVP_MAGIC_SKILL_DMG_BONUS, 1, null, null);
            damage /= target.calcStat(Stats.PVP_MAGIC_SKILL_DEFENCE_BONUS, 1, null, null);
        }

        double magic_rcpt = target.calcStat(Stats.MAGIC_RESIST, attacker, skill) - attacker.calcStat(Stats.MAGIC_POWER, target, skill);
        double lvlMod =  4. * Math.max(1., target.getLevel() >= 80 ? (levelDiff - 4) * 1.6 : (levelDiff - 14) * 2);
        double failChance = gradePenalty ? 95. : Math.min(lvlMod * (1. + magic_rcpt / 100.), 95.);
        double resistChance = gradePenalty ? 95. : 5 * Math.max(levelDiff - 10, 1);

        if (attacker.isPlayer() && ((Player)attacker).isDebug())
            attacker.sendMessage("Fail chance " + (int)failChance + "/" + (int)resistChance);

        if(Rnd.chance(failChance))
        {
            if(Rnd.chance(resistChance))
            {
                damage = 0;
                SystemMessage msg = new SystemMessage(SystemMsg.C1_RESISTED_C2S_MAGIC).addName(target).addName(attacker);
                attacker.sendPacket(msg);
                target.sendPacket(msg);
            }
            else
            {
                damage /= 2;
                SystemMessage msg = new SystemMessage(SystemMsg.DAMAGE_IS_DECREASED_BECAUSE_C1_RESISTED_C2S_MAGIC).addName(target).addName(attacker);
                attacker.sendPacket(msg);
                target.sendPacket(msg);
            }
        }

        attacker.displayGiveDamageMessage(target, (int) damage, crit, false, false, true);

        if(calcCastBreak(target, crit))
            target.abortCast(false, true);

        return damage;
    }

    public static void calcStunBreak(Creature target, boolean crit)
    {
        if (target.isStunned() && Rnd.chance(crit ? 75 : 10))
            for (Effect e : target.getEffectList().getAllEffects())
                if (e.getEffectType() == EffectType.Stun)
                {
                    e.exit();
                    if (target.isPlayer())
                        target.sendPacket(new SystemMessage(SystemMsg.THE_EFFECT_OF_S1_HAS_BEEN_REMOVED).addSkillName(e.getSkill()));
                }

        // TODO: DS: убрать хардкод
        if (Rnd.chance(crit ? 10 : 2)) // real_target
            for (Effect e : target.getEffectList().getAllEffects())
                if (e.getSkill().getId() == 522)
                {
                    e.exit();
                    if (target.isPlayer())
                        target.sendPacket(new SystemMessage(SystemMsg.THE_EFFECT_OF_S1_HAS_BEEN_REMOVED).addSkillName(e.getSkill()));
                }
    }

    /** Returns true in case of fatal blow success */
    public static boolean calcBlow(Creature activeChar, Creature target, SkillEntry skill)
    {
        WeaponTemplate weapon = activeChar.getActiveWeaponItem();

        double base_weapon_crit = weapon == null ? 4. : weapon.getCritical();
        double crit_height_bonus = 0.008 * Math.min(25, Math.max(-25, target.getZ() - activeChar.getZ())) + 1.1;
        double buffs_mult = activeChar.calcStat(Stats.FATALBLOW_RATE, target, skill);
        double skill_mod = skill.getTemplate().isBehind() ? 6 : 5; // CT 2.3 blowrate increase

        double chance = base_weapon_crit * buffs_mult * crit_height_bonus * skill_mod;

        if(!target.isInCombat())
            chance *= 1.1;

        switch(PositionUtils.getDirectionTo(target, activeChar))
        {
            case BEHIND:
                chance *= 1.3;
                break;
            case SIDE:
                chance *= 1.1;
                break;
            case FRONT:
                if(skill.getTemplate().isBehind())
                    chance = 3.0;
                break;
        }
        chance = Math.min(skill.getTemplate().isBehind() ? 100 : 80, chance);
        return Rnd.chance(chance);
    }

    /** Возвращает шанс крита в процентах */
    public static double calcCrit(Creature attacker, Creature target, SkillEntry skill, boolean blow)
    {
        if(attacker.isPlayer() && attacker.getActiveWeaponItem() == null)
            return 0;
        if(skill != null)
            return skill.getTemplate().getCriticalRate() * (blow ? BaseStats.DEX.calcBonus(attacker) : BaseStats.STR.calcBonus(attacker)) * 0.01 * attacker.calcStat(Stats.SKILL_CRIT_CHANCE_MOD, target, skill);

        double rate = attacker.getCriticalHit(target, null) * 0.01 * target.calcStat(Stats.CRIT_CHANCE_RECEPTIVE, attacker, skill);

        switch(PositionUtils.getDirectionTo(target, attacker))
        {
            case BEHIND:
                rate *= 1.4;
                break;
            case SIDE:
                rate *= 1.2;
                break;
        }

        return rate / 10;
    }

    public static boolean calcMCrit(double mRate)
    {
        // floating point random gives more accuracy calculation, because argument also floating point
        return Rnd.get() * 100 <= Math.min(Config.LIM_MCRIT, mRate);
    }

    public static boolean calcCastBreak(Creature target, boolean crit)
    {
        if(target == null || target.isDamageBlocked() || target.isRaid() || !target.isCastingNow())
            return false;
        SkillEntry skill = target.getCastingSkill();
        if (skill == null)
            return false;
        if(skill.getTemplate().getMagicType() == SkillMagicType.PHYSIC || skill.getTemplate().getMagicType() == SkillMagicType.MUSIC)//(skill.getSkillType() == SkillType.TAKECASTLE || skill.getSkillType() == SkillType.TAKEFORTRESS || skill.getSkillType() == SkillType.TAKEFLAG))
            return false;
        return Rnd.chance(target.calcStat(Stats.CAST_INTERRUPT, crit ? 75 : 10, null, skill));
    }

    /** Calculate delay (in milliseconds) before next ATTACK */
    public static int calcPAtkSpd(double rate)
    {
        return (int) (500000 / rate); // в миллисекундах поэтому 500*1000
    }

    /** Calculate delay (in milliseconds) for skills cast */
    public static int calcMAtkSpd(Creature attacker, Skill skill, double skillTime)
    {
        if(skill.isMagic())
            return (int) (skillTime * 333 / Math.max(attacker.getMAtkSpd(), 1));
        return (int) (skillTime * 333 / Math.max(attacker.getPAtkSpd(true), 1));
    }

    /** Calculate reuse delay (in milliseconds) for skills */
    public static long calcSkillReuseDelay(Creature actor, SkillEntry skill)
    {
        long reuseDelay = skill.getTemplate().getReuseDelay();
        if(actor.isMonster())
            reuseDelay = skill.getTemplate().getReuseForMonsters();
        if(skill.getTemplate().isReuseDelayPermanent() || skill.getTemplate().isHandler() || skill.getTemplate().isItemSkill())
            return reuseDelay;
        if(actor.getSkillMastery(skill.getTemplate()).hasZeroReuse())
            return 0;
        if (skill.getTemplate().isMusic())
            return (long) actor.calcStat(Stats.MUSIC_REUSE_RATE, reuseDelay, null, skill);
        if(skill.getTemplate().isMagic())
            return (long) actor.calcStat(Stats.MAGIC_REUSE_RATE, reuseDelay, null, skill);
        return (long) actor.calcStat(Stats.PHYSIC_REUSE_RATE, reuseDelay, null, skill);
    }

    /** Returns true if hit missed (target evaded) */
    public static boolean calcHitMiss(Creature attacker, Creature target)
    {
        int chanceToHit = 88 + 2 * (attacker.getAccuracy() - target.getEvasionRate(attacker));

        chanceToHit = Math.max(chanceToHit, 28);
        chanceToHit = Math.min(chanceToHit, 98);

        PositionUtils.TargetDirection direction = PositionUtils.getDirectionTo(attacker, target);
        switch(direction)
        {
            case BEHIND:
                chanceToHit *= 1.2;
                break;
            case SIDE:
                chanceToHit *= 1.1;
                break;
        }
        return !Rnd.chance(chanceToHit);
    }

    /** Returns true if shield defence successfull */
    public static boolean calcShldUse(Creature attacker, Creature target)
    {
        WeaponTemplate template = target.getSecondaryWeaponItem();
        if(template == null || template.getItemType() != WeaponTemplate.WeaponType.NONE)
            return false;
        int angle = (int) target.calcStat(Stats.SHIELD_ANGLE, attacker, null);
        if(!PositionUtils.isFacing(target, attacker, angle))
            return false;
        return Rnd.chance((int)target.calcStat(Stats.SHIELD_RATE, attacker, null));
    }

    public static boolean[] isDebugEnabled(Creature caster, Creature target)
    {
        if (Config.ALT_DEBUG_ENABLED)
        {
            // Включена ли отладка на кастере
            final boolean debugCaster = caster.getPlayer() != null && caster.getPlayer().isDebug();
            // Включена ли отладка на таргете
            final boolean debugTarget = target.getPlayer() != null && target.getPlayer().isDebug();
            // Разрешена ли отладка в PvP
            if (Config.ALT_DEBUG_PVP_ENABLED && (debugCaster && debugTarget) && (!Config.ALT_DEBUG_PVP_DUEL_ONLY || (caster.getPlayer().isInDuel() && target.getPlayer().isInDuel())))
                return new boolean[]{true, debugCaster, debugTarget};
            // Включаем отладку в PvE если разрешено
            if (Config.ALT_DEBUG_PVE_ENABLED && ((debugCaster && target.isMonster()) || (debugTarget && caster.isMonster())))
                return new boolean[]{true, debugCaster, debugTarget};
        }

        return DEBUG_DISABLED;
    }

    public static boolean calcSkillSuccess(Env env, EffectTemplate et, int spiritshot)
    {
        if(env.value == -1)
            return true;

        env.value = Math.max(Math.min(env.value, 150), 1); // На всякий случай
        final double base = env.value; // Запоминаем базовый шанс (нужен позже)

        final SkillEntry skill = env.skill;
        if(!skill.getTemplate().isOffensive())
            return Rnd.chance(env.value);

        final Creature caster = env.character;
        final Creature target = env.target;

        final boolean[] debug = isDebugEnabled(caster, target);
        final boolean debugGlobal = debug[0];
        final boolean debugCaster = debug[1];
        final boolean debugTarget = debug[2];

        double statMod = 1.;
        if(skill.getTemplate().getSaveVs() != null)
        {
            statMod = skill.getTemplate().getSaveVs().calcChanceMod(target);
            env.value *= statMod; // Бонус от MEN/CON/etc
        }

        env.value = Math.max(env.value, 1);

        double mAtkMod = 1.;
        int ssMod = 0;
        if(skill.getTemplate().isMagic() && (et == null || et.chance() < 0)) // Этот блок только для магических скиллов, эффекты с отдельным шансом тоже пропускаются
        {
            int mdef = Math.max(1, target.getMDef(target, skill)); // Вычисляем mDef цели
            double matk = caster.getMAtk(target, skill);

            if(skill.getTemplate().isSSPossible())
            {
                switch (spiritshot)
                {
                case ItemInstance.CHARGED_BLESSED_SPIRITSHOT:
                    ssMod = 4;
                    break;
                case ItemInstance.CHARGED_SPIRITSHOT:
                    ssMod = 2;
                    break;
                default:
                    ssMod = 1;
                }
                matk *= ssMod;
            }

            mAtkMod = Config.SKILLS_CHANCE_MOD * Math.pow(matk, Config.SKILLS_CHANCE_POW) / mdef;

            /*
            if (mAtkMod < 0.7)
                mAtkMod = 0.7;
            else if (mAtkMod > 1.4)
                mAtkMod = 1.4;
            */

            env.value *= mAtkMod;
            env.value = Math.max(env.value, 1);
        }

        double lvlDependMod = skill.getTemplate().getLevelModifier();
        if (lvlDependMod != 0)
        {
            final int attackLevel =  skill.getTemplate().getMagicLevel() > 0 ? skill.getTemplate().getMagicLevel() : caster.getLevel();
            /*final int delta = attackLevel - target.getLevel();
            lvlDependMod = delta / 5;
            lvlDependMod = lvlDependMod * 5;
            if (lvlDependMod != delta)
                lvlDependMod = delta < 0 ? lvlDependMod - 5 : lvlDependMod + 5;

            env.value += lvlDependMod;*/
            lvlDependMod = 1. + (attackLevel - target.getLevel()) * 0.03 * lvlDependMod;
            if (lvlDependMod < 0)
                lvlDependMod = 0;
            else if (lvlDependMod > 2)
                lvlDependMod = 2;

            env.value *= lvlDependMod;
        }

        double vulnMod = 0;
        double profMod = 0;
        double resMod = 1.;
        double debuffMod = 1.;
        if(!skill.getTemplate().isIgnoreResists())
        {
            if (et == null || et.chance() < 0) // Эффекты с индивидуальным шансом - не дебафы (сброс цели). TODO: отдельный флаг
            {
                debuffMod = 1. - (target.calcStat(Stats.DEBUFF_RESIST, 100., caster, skill) - 100.) / 120.;

                if(debuffMod != 1) // Внимание, знак был изменен на противоположный !
                {
                    if (debuffMod == Double.NEGATIVE_INFINITY)
                    {
                        if (debugGlobal)
                        {
                            if (debugCaster)
                                caster.getPlayer().sendMessage("Full debuff immunity");
                            if (debugTarget)
                                target.getPlayer().sendMessage("Full debuff immunity");
                        }
                        return false;
                    }
                    if (debuffMod == Double.POSITIVE_INFINITY)
                    {
                        if (debugGlobal)
                        {
                            if (debugCaster)
                                caster.getPlayer().sendMessage("Full debuff vulnerability");
                            if (debugTarget)
                                target.getPlayer().sendMessage("Full debuff vulnerability");
                        }
                        return true;
                    }

                    debuffMod = Math.max(debuffMod, 0);
                    env.value *= debuffMod;
                }
            }

            SkillTrait trait = skill.getTemplate().getTraitType();
            if (trait != null)
            {
                vulnMod = trait.calcVuln(env);
                profMod = trait.calcProf(env);

                final double maxResist = 90 + profMod * 0.85;
                resMod = (maxResist - vulnMod) / 60.;
            }

            if(resMod != 1) // Внимание, знак был изменен на противоположный !
            {
                if (resMod == Double.NEGATIVE_INFINITY)
                {
                    if (debugGlobal)
                    {
                        if (debugCaster)
                            caster.getPlayer().sendMessage("Full immunity");
                        if (debugTarget)
                            target.getPlayer().sendMessage("Full immunity");
                    }
                    return false;
                }
                if (resMod == Double.POSITIVE_INFINITY)
                {
                    if (debugGlobal)
                    {
                        if (debugCaster)
                            caster.getPlayer().sendMessage("Full vulnerability");
                        if (debugTarget)
                            target.getPlayer().sendMessage("Full vulnerability");
                    }
                    return true;
                }

                resMod = Math.max(resMod, 0);
                env.value *= resMod;
            }
        }

        double elementMod = 0;
        final Element element = skill.getTemplate().getElement();
        if (element != Element.NONE)
        {
            elementMod = skill.getTemplate().getElementPower();
            Element attackElement = getAttackElement(caster, target);
            if (attackElement == element)
                elementMod += caster.calcStat(element.getAttack(), 0, target, null);

            elementMod = getElementMod(elementMod, target.calcStat(element.getDefence(), 0, caster, null), caster.isPlayer() && target.isPlayer());
            env.value *= elementMod;
        }

        //if(skill.isSoulBoost()) // Бонус от душ камаелей
        //    env.value *= 0.85 + 0.06 * Math.min(character.getConsumedSouls(), 5);

        env.value = Math.max(env.value, Math.min(base, Config.SKILLS_CHANCE_MIN)); // Если базовый шанс более Config.SKILLS_CHANCE_MIN, то при небольшой разнице в уровнях, делаем кап снизу.
        env.value = Math.max(Math.min(env.value, Config.SKILLS_CHANCE_CAP), 1); // Применяем кап
        final boolean result = Rnd.chance((int)env.value);

        if (debugGlobal)
        {
            StringBuilder stat = new StringBuilder(100);
            stat.append(skill.getId());
            stat.append("/");               
            stat.append(skill.getDisplayLevel());
            stat.append(" ");               
            if (et == null)
                stat.append(skill.getTemplate().getName());
            else
                stat.append(et._effectType.name());
            stat.append(" AR:");
            stat.append((int)base);
            stat.append(" ");               
            if (skill.getTemplate().getSaveVs() != null)
            {
                stat.append(skill.getTemplate().getSaveVs().name());
                stat.append(":");               
                stat.append(String.format("%1.1f", statMod));
            }
            if (skill.getTemplate().isMagic())
            {
                stat.append(" ");               
                stat.append(" mAtk:");
                stat.append(String.format("%1.1f", mAtkMod));
                stat.append(" SS:");
                stat.append(ssMod);
            }
            if (skill.getTemplate().getTraitType() != null)
            {
                stat.append(" ");               
                stat.append(skill.getTemplate().getTraitType().name());
            }
            stat.append(" ");
            stat.append(String.format("%1.1f", resMod));
            stat.append("(");
            stat.append(String.format("%1.1f", profMod));
            stat.append("/");
            stat.append(String.format("%1.1f", vulnMod));
            if (debuffMod != 0)
            {
                stat.append("+");
                stat.append(String.format("%1.1f", debuffMod));
            }
            stat.append(") lvl:");
            stat.append(String.format("%1.1f", lvlDependMod));
            stat.append(" elem:");
            stat.append(String.format("%1.1f", elementMod));
            stat.append(" Chance:");
            stat.append(String.format("%1.1f", env.value));
            if (!result)
                stat.append(" failed");

            // отсылаем отладочные сообщения
            if (debugCaster)
                caster.getPlayer().sendMessage(stat.toString());
            if (debugTarget)
                target.getPlayer().sendMessage(stat.toString());
        }
        return result;
    }

    public static boolean calcSkillSuccess(Creature player, Creature target, SkillEntry skill, int activateRate)
    {
        Env env = new Env();
        env.character = player;
        env.target = target;
        env.skill = skill;
        env.value = activateRate;
        return calcSkillSuccess(env, null, player.getChargedSpiritShot(false));
    }

    public static void calcSkillMastery(SkillEntry skill, Creature activeChar)
    {
        if(skill.getTemplate().isHandler())
            return;

        //Skill id 330 for fighters, 331 for mages
        //Actually only GM can have 2 skill masteries, so let's make them more lucky ^^
        if((activeChar.getSkillLevel(331) > 0 && activeChar.calcStat(Stats.SKILL_MASTERY, activeChar.getINT(), null, skill) >= Rnd.get(5000)) || (activeChar.getSkillLevel(330) > 0 && activeChar.calcStat(Stats.SKILL_MASTERY, activeChar.getSTR(), null, skill) >= Rnd.get(5000)))
            activeChar.setSkillMastery(skill.getTemplate());
    }

    public static double calcDamageResists(SkillEntry skill, Creature attacker, Creature defender, double value)
    {
        if(attacker == defender) // это дамаг от местности вроде ожога в лаве, наносится от своего имени
            return value; // TODO: по хорошему надо учитывать защиту, но поскольку эти скиллы немагические то надо делать отдельный механизм

        if(attacker.isBoss())
            value *= Config.RATE_EPIC_ATTACK;
        else if(attacker.isRaid() || attacker instanceof ReflectionBossInstance)
            value *= Config.RATE_RAID_ATTACK;

        if(defender.isBoss())
            value /= Config.RATE_EPIC_DEFENSE;
        else if(defender.isRaid() || defender instanceof ReflectionBossInstance)
            value /= Config.RATE_RAID_DEFENSE;

        Player pAttacker = attacker.getPlayer();

        // если уровень игрока ниже чем на 2 и более уровней моба 78+, то его урон по мобу снижается
        int diff = defender.getLevel() - (pAttacker != null ? pAttacker.getLevel() : attacker.getLevel());
        if(attacker.isPlayable() && defender.isMonster() && defender.getLevel() >= 78 && diff > 2)
            value *= .7 / Math.pow(diff - 2, .25);

        Element element = Element.NONE;
        double power = 0.;

        // использует элемент умения
        if(skill != null)
        {
            element = skill.getTemplate().getElement();
            power = skill.getTemplate().getElementPower();
        }
        // используем максимально эффективный элемент
        else
            element = getAttackElement(attacker, defender);

        if(element == Element.NONE)
            return value;

        if(pAttacker != null && pAttacker.isGM() && Config.DEBUG)
        {
            pAttacker.sendMessage("Element: " + element.name());
            pAttacker.sendMessage("Attack: " + attacker.calcStat(element.getAttack(), power, defender, skill));
            pAttacker.sendMessage("Defence: " + defender.calcStat(element.getDefence(), 0., attacker, skill));
            pAttacker.sendMessage("Modifier: " + getElementMod(attacker.calcStat(element.getAttack(), power, defender, skill), defender.calcStat(element.getDefence(), 0., attacker, skill), attacker.isPlayer() && defender.isPlayer()));
        }

        return value * getElementMod(attacker.calcStat(element.getAttack(), power, defender, skill), defender.calcStat(element.getDefence(), 0., attacker, skill), attacker.isPlayer() && defender.isPlayer());
    }

    /**
     * Возвращает множитель для атаки из значений атакующего и защитного элемента.
     * <br /><br />
     * Диапазон от 1.0 до 1.7 (Freya)
     * <br /><br />
     * @param attack значение атаки
     * @param defense значение защиты
     * @param isPvP если оба игроки (самоны не считаются)
     * @return множитель
     */
    private static double getElementMod(double attack, double defense, boolean isPvP)
    {
        double diff = attack - defense;
        if(diff <= 0)
            return 1.0;
        else if(diff < 50)
            return 1.0 + diff * 0.003948;
        else if(diff < 150)
            return 1.2;
        else if(diff < 300)
            return 1.4;
        else
            return 1.7;
    }

    /**
     * Возвращает максимально эффективный атрибут, при атаке цели
     * @param attacker
     * @param target
     * @return
     */
    public static Element getAttackElement(Creature attacker, Creature target)
    {
        double val, max = Double.MIN_VALUE;
        Element result = Element.NONE;
        for(Element e : Element.VALUES)
        {
            val = attacker.calcStat(e.getAttack(), 0., target, null);
            if(val <= 0.)
                continue;

            if(target != null)
                val -= target.calcStat(e.getDefence(), 0., attacker, null);

            if(val > max)
            {
                result = e;
                max = val;
            }
        }

        return result;
    }
}
 
  • Мне нравится
Реакции: Caper111

    Caper111

    Баллов: 1
    За эпический ответ
Ну наверное что-то типа этого:
Код:
package org.mmocore.gameserver.stats;

import org.mmocore.commons.util.Rnd;
import org.mmocore.gameserver.Config;
import org.mmocore.gameserver.model.Creature;
import org.mmocore.gameserver.model.Effect;
import org.mmocore.gameserver.model.Player;
import org.mmocore.gameserver.model.Skill;
import org.mmocore.gameserver.model.Skill.SkillMagicType;
import org.mmocore.gameserver.model.base.BaseStats;
import org.mmocore.gameserver.model.base.Element;
import org.mmocore.gameserver.model.base.SkillTrait;
import org.mmocore.gameserver.model.instances.ReflectionBossInstance;
import org.mmocore.gameserver.model.items.ItemInstance;
import org.mmocore.gameserver.network.l2.components.SystemMsg;
import org.mmocore.gameserver.network.l2.s2c.SystemMessage;
import org.mmocore.gameserver.skills.EffectType;
import org.mmocore.gameserver.skills.SkillEntry;
import org.mmocore.gameserver.skills.effects.EffectTemplate;
import org.mmocore.gameserver.templates.item.WeaponTemplate;
import org.mmocore.gameserver.utils.PositionUtils;

public class Formulas
{
    private static final boolean[] DEBUG_DISABLED = { false, false, false};

    public static double calcHpRegen(Creature cha)
    {
        double init;
        if(cha.isPlayer())
            init = (cha.getLevel() <= 10 ? 1.5 + cha.getLevel() / 20. : 1.4 + cha.getLevel() / 10.) * cha.getLevelMod();
        else
            init = cha.getTemplate().baseHpReg;

        if(cha.isPlayable())
        {
            init *= BaseStats.CON.calcBonus(cha);
            if(cha.isSummon())
                init *= 2;
        }

        return cha.calcStat(Stats.REGENERATE_HP_RATE, init, null, null);
    }

    public static double calcMpRegen(Creature cha)
    {
        double init;
        if(cha.isPlayer())
            init = (.87 + cha.getLevel() * .03) * cha.getLevelMod();
        else
            init = cha.getTemplate().baseMpReg;

        if(cha.isPlayable())
        {
            init *= BaseStats.MEN.calcBonus(cha);
            if(cha.isSummon())
                init *= 2;
        }

        return cha.calcStat(Stats.REGENERATE_MP_RATE, init, null, null);
    }

    public static double calcCpRegen(Creature cha)
    {
        double init = (1.5 + cha.getLevel() / 10) * cha.getLevelMod() * BaseStats.CON.calcBonus(cha);
        return cha.calcStat(Stats.REGENERATE_CP_RATE, init, null, null);
    }

    public static class AttackInfo
    {
        public double damage = 0;
        public double defence = 0;
        public double crit_static = 0;
        public double death_rcpt = 0;
        public double lethal1 = 0;
        public double lethal2 = 0;
        public double lethal_dmg = 0;
        public boolean crit = false;
        public boolean shld = false;
        public boolean lethal = false;
        public boolean miss = false;
    }

    /**
     * Для простых ударов
     * patk = patk
     * При крите простым ударом:
     * patk = patk * (1 + crit_damage_rcpt) * crit_damage_mod + crit_damage_static
     * Для blow скиллов
     * TODO
     * Для скилловых критов, повреждения просто удваиваются, бафы не влияют (кроме blow, для них выше)
     * patk = (1 + crit_damage_rcpt) * (patk + skill_power)
     * Для обычных атак
     * damage = patk * ss_bonus * 70 / pdef
     */
    public static AttackInfo calcPhysDam(Creature attacker, Creature target, SkillEntry skill, boolean dual, boolean blow, boolean ss, boolean onCrit)
    {
        AttackInfo info = new AttackInfo();

        info.damage = attacker.getPAtk(target);
        info.defence = target.getPDef(attacker);
        info.crit_static = attacker.calcStat(Stats.CRITICAL_DAMAGE_STATIC, target, skill);
        info.death_rcpt = 0.01 * target.calcStat(Stats.DEATH_VULNERABILITY, attacker, skill);
        info.lethal1 = skill == null ? 0 : skill.getTemplate().getLethal1() * info.death_rcpt;
        info.lethal2 = skill == null ? 0 : skill.getTemplate().getLethal2() * info.death_rcpt;
        info.crit = Rnd.chance(calcCrit(attacker, target, skill, blow));
        info.shld = (skill == null || !skill.getTemplate().getShieldIgnore()) && Formulas.calcShldUse(attacker, target);
        info.lethal = false;
        info.miss = false;
        boolean isPvP = attacker.isPlayable() && target.isPlayable();

        if(info.shld)
            info.defence += target.getShldDef();

        info.defence = Math.max(info.defence, 1);

        if(skill != null)
        {
            final SkillTrait trait = skill.getTemplate().getTraitType();
            if (trait != null)
            {
                final Env env = new Env(attacker, target, skill);
                double traitMul = 1. + (trait.calcProf(env) - trait.calcVuln(env)) / 100.;
                if (traitMul == Double.NEGATIVE_INFINITY) // invul
                {
                    info.damage = 0;
                    return info;
                }
                /*else if (traitMul > 2.) // DS: нужны тесты
                    traitMul = 2.;
                else if (traitMul < 0.05)
                    traitMul = 0.05;

                power *= traitMul;*/
            }

            if(!blow && !target.isLethalImmune() && target.getLevel() - skill.getTemplate().getMagicLevel() <= 5) // считаем леталы для не blow скиллов
                if(info.lethal1 > 0 && Rnd.chance(info.lethal1))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentCp();
                        target.sendPacket(SystemMsg.YOUR_CP_WAS_DRAINED_BECAUSE_YOU_WERE_HIT_WITH_A_CP_SIPHON_SKILL);
                    }
                    else
                        info.lethal_dmg = target.getCurrentHp() / 2;
                    attacker.sendPacket(SystemMsg.CP_SIPHON);
                }
                else if(info.lethal2 > 0 && Rnd.chance(info.lethal2))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentHp() + target.getCurrentCp() - 1;
                        target.sendPacket(SystemMsg.LETHAL_STRIKE);
                    }
                    else
                        info.lethal_dmg = target.getCurrentHp() - 1;
                    attacker.sendPacket(SystemMsg.YOUR_LETHAL_STRIKE_WAS_SUCCESSFUL);
                }

            // если скилл не имеет своей силы дальше идти бесполезно, можно сразу вернуть дамаг от летала
            if(skill.getTemplate().getPower(target) == 0)
            {
                info.damage = 0; // обычного дамага в этом случае не наносится
                return info;
            }

            if(blow && !skill.getTemplate().isBehind() && ss) // Для обычных blow не влияет на power
                info.damage *= 2.04;

            // Для зарядок влияет на суммарный бонус
            if(skill.getTemplate().isChargeBoost())
                info.damage = attacker.calcStat(Stats.SKILL_POWER, info.damage + skill.getTemplate().getPower(target), null, null);
            else
                info.damage += attacker.calcStat(Stats.SKILL_POWER, skill.getTemplate().getPower(target), null, null);

            if(blow && skill.getTemplate().isBehind() && ss) // Для backstab влияет на power, но меньше множитель
                info.damage *= 1.5;

            //Заряжаемые скилы имеют постоянный урон
            if(!skill.getTemplate().isChargeBoost())
                info.damage *= 1 + (Rnd.get() * attacker.getRandomDamage() * 2 - attacker.getRandomDamage()) / 100;

            if(blow)
            {
                // DS: Focus Death и Focus Power умножаются на 0.5 в датапаке
                info.damage *= 0.01 * attacker.calcStat(Stats.CRITICAL_DAMAGE, target, skill);
                info.damage = target.calcStat(Stats.CRIT_DAMAGE_RECEPTIVE, info.damage, attacker, skill);
                info.damage += 6.1 * info.crit_static;
            }

            if(skill.getTemplate().isChargeBoost())
                info.damage *= 0.8 + 0.2 * (attacker.getIncreasedForce() + Math.max(skill.getTemplate().getNumCharges(), 0));
            else if(skill.getTemplate().isSoulBoost())
                info.damage *= 1.0 + 0.06 * Math.min(attacker.getConsumedSouls(), 5);

            // Gracia Physical Skill Damage Bonus
            info.damage *= 1.10113;

            if(info.crit)
                info.damage *= 2.;
        }
        else
        {
            info.damage *= 1 + (Rnd.get() * attacker.getRandomDamage() * 2 - attacker.getRandomDamage()) / 100;

            if(dual)
                info.damage /= 2.;

            if(info.crit)
            {
                info.damage *= 0.01 * attacker.calcStat(Stats.CRITICAL_DAMAGE, target, skill);
                info.damage = 2 * target.calcStat(Stats.CRIT_DAMAGE_RECEPTIVE, info.damage, attacker, skill);
                info.damage += info.crit_static;
            }
        }

        if(info.crit)
        {
            // шанс абсорбации души (без анимации) при крите, если Soul Mastery 4го уровня или более
            int chance = attacker.getSkillLevel(Skill.SKILL_SOUL_MASTERY);
            if(chance > 0)
            {
                if(chance >= 21)
                    chance = 30;
                else if(chance >= 15)
                    chance = 25;
                else if(chance >= 9)
                    chance = 20;
                else if(chance >= 4)
                    chance = 15;
                if(Rnd.chance(chance))
                    attacker.setConsumedSouls(attacker.getConsumedSouls() + 1, null);
            }
        }

        // у зарядок нет бонусов от положения цели
        if (skill == null || !skill.getTemplate().isChargeBoost())
            switch(PositionUtils.getDirectionTo(target, attacker))
            {
                case BEHIND:
                    info.damage *= 1.2;
                    break;
                case SIDE:
                    info.damage *= 1.1;
                    break;
            }

        if(ss && !blow)
            info.damage *= 2.0;

        info.damage *= 70. / info.defence;
        info.damage = attacker.calcStat(Stats.PHYSICAL_DAMAGE, info.damage, target, skill);

        if(info.shld && Rnd.chance(5))
            info.damage = 1;

        if(isPvP)
        {
            if(skill == null)
            {
                info.damage *= attacker.calcStat(Stats.PVP_PHYS_DMG_BONUS, 1, null, null);
                info.damage /= target.calcStat(Stats.PVP_PHYS_DEFENCE_BONUS, 1, null, null);
            }
            else
            {
                info.damage *= attacker.calcStat(Stats.PVP_PHYS_SKILL_DMG_BONUS, 1, null, null);
                info.damage /= target.calcStat(Stats.PVP_PHYS_SKILL_DEFENCE_BONUS, 1, null, null);
            }
        }

        // Тут проверяем только если skill != null, т.к. L2Character.onHitTimer не обсчитывает дамаг.
        if(skill != null)
        {
            if(info.shld)
                if(info.damage == 1)
                    target.sendPacket(SystemMsg.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
                else
                    target.sendPacket(SystemMsg.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);

            // Уворот от физ скилов уводит атаку в 0
            if(info.damage > 1 && !skill.getTemplate().hasNotSelfEffects() && Rnd.chance(target.calcStat(Stats.PSKILL_EVASION, 0, attacker, skill)))
            {
                attacker.sendPacket(new SystemMessage(SystemMsg.C1_DODGES_THE_ATTACK).addName(target));
                target.sendPacket(new SystemMessage(SystemMsg.YOU_HAVE_AVOIDED_C1S_ATTACK).addName(attacker));
                info.damage = 0;
            }

            if(info.damage > 1 && skill.getTemplate().isDeathlink())
                info.damage *= 1.8 * (1.0 - attacker.getCurrentHpRatio());

            if(onCrit && !calcBlow(attacker, target, skill))
            {
                info.miss = true;
                info.damage = 0;
                //attacker.sendPacket(new SystemMessage(SystemMsg.C1S_ATTACK_WENT_ASTRAY).addName(attacker));
            }

            if(blow && target.getLevel() - skill.getTemplate().getMagicLevel() <= 5)
                if(info.lethal1 > 0 && Rnd.chance(info.lethal1))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentCp();
                        target.sendPacket(SystemMsg.YOUR_CP_WAS_DRAINED_BECAUSE_YOU_WERE_HIT_WITH_A_CP_SIPHON_SKILL);
                    }
                    else if(target.isLethalImmune())
                        info.damage *= 2;
                    else
                        info.lethal_dmg = target.getCurrentHp() / 2;
                    attacker.sendPacket(SystemMsg.CP_SIPHON);
                }
                else if(info.lethal2 > 0 && Rnd.chance(info.lethal2))
                {
                    if(target.isPlayer())
                    {
                        info.lethal = true;
                        info.lethal_dmg = target.getCurrentHp() + target.getCurrentCp() - 1;
                        target.sendPacket(SystemMsg.LETHAL_STRIKE);
                    }
                    else if(target.isLethalImmune())
                        info.damage *= 3;
                    else
                        info.lethal_dmg = target.getCurrentHp() - 1;
                    attacker.sendPacket(SystemMsg.YOUR_LETHAL_STRIKE_WAS_SUCCESSFUL);
                }

            if(info.damage > 0)
                attacker.displayGiveDamageMessage(target, (int) info.damage, info.crit || blow, false, false, false);

            calcStunBreak(target, info.crit);

            if(calcCastBreak(target, info.crit))
                target.abortCast(false, true);
        }

        return info;
    }

    public static double calcMagicDam(Creature attacker, Creature target, SkillEntry skill, int sps, boolean toMp)
    {
        boolean isPvP = attacker.isPlayable() && target.isPlayable();
        // Параметр ShieldIgnore для магических скиллов инвертирован
        boolean shield = skill.getTemplate().getShieldIgnore() && calcShldUse(attacker, target);

        double mAtk = attacker.getMAtk(target, skill);

        if(sps == 2)
            mAtk *= 4;
        else if(sps == 1)
            mAtk *= 2;

        double mdef = target.getMDef(null, skill);

        if(shield)
            mdef += target.getShldDef();
        if(mdef == 0)
            mdef = 1;

        double power = skill.getTemplate().getPower(target);

        boolean gradePenalty = attacker.isPlayer() && ((Player)attacker).getWeaponsExpertisePenalty() > 0;

        final SkillTrait trait = skill.getTemplate().getTraitType();
        if (trait != null)
        {
            final Env env = new Env(attacker, target, skill);
            double traitMul = 1. + (trait.calcProf(env) - trait.calcVuln(env)) / 100.;
            if (traitMul == Double.NEGATIVE_INFINITY) // invul
                return 0;
            else if (traitMul > 2.)
                traitMul = 2.;
            else if (traitMul < 0.05)
                traitMul = 0.05;

            power *= traitMul;
        }

        double lethalDamage = 0;

        if (target.getLevel() - skill.getTemplate().getMagicLevel() <= 5 && !gradePenalty)
        {
            if(skill.getTemplate().getLethal1() > 0 && Rnd.chance(skill.getTemplate().getLethal1()))
            {
                if(target.isPlayer())
                {
                    lethalDamage = target.getCurrentCp();
                    target.sendPacket(SystemMsg.YOUR_CP_WAS_DRAINED_BECAUSE_YOU_WERE_HIT_WITH_A_CP_SIPHON_SKILL);
                }
                else if(!target.isLethalImmune())
                    lethalDamage = target.getCurrentHp() / 2;
                else
                    power *= 2;
                attacker.sendPacket(SystemMsg.CP_SIPHON);
            }
            else if(skill.getTemplate().getLethal2() > 0 && Rnd.chance(skill.getTemplate().getLethal2()))
            {
                if(target.isPlayer())
                {
                    lethalDamage = target.getCurrentHp() + target.getCurrentCp() - 1;
                    target.sendPacket(SystemMsg.LETHAL_STRIKE);
                }
                else if(!target.isLethalImmune())
                    lethalDamage = target.getCurrentHp() - 1;
                else
                    power *= 3;
                attacker.sendPacket(SystemMsg.YOUR_LETHAL_STRIKE_WAS_SUCCESSFUL);
            }
        }

        if(power == 0)
        {
            if(lethalDamage > 0)
                attacker.displayGiveDamageMessage(target, (int) lethalDamage, false, false, false, false);
            return lethalDamage;
        }

        if(skill.getTemplate().isSoulBoost())
            power *= 1.0 + 0.0225 * Math.min(attacker.getConsumedSouls(), 5);

        boolean crit = false;
        double damage = power * Math.sqrt(mAtk) / mdef;
        if (toMp)
        {
            if (isPvP)
                damage *= target.getMaxMp() / 97.;
            else
            {
                damage *= 91.;
                damage = Math.max(1, damage / 2.);
            }
        }
        else
            damage *= 91.;

        if (skill.getTemplate().getMatak() == 0) // у кубиков нет рандомдамага и критов
        {
            damage *= 1 + (Rnd.get() * attacker.getRandomDamage() * 2 - attacker.getRandomDamage()) / 100;
            crit = calcMCrit(attacker.getMagicCriticalRate(target, skill));          
        }

        if(crit)
            damage *= attacker.calcStat(Stats.MCRITICAL_DAMAGE, attacker.isPlayable() && target.isPlayable() ? 2.5 : 3., target, skill);

        damage = attacker.calcStat(Stats.MAGIC_DAMAGE, damage, target, skill);

        if(shield)
        {
            if(Rnd.chance(5))
            {
                damage = 0;
                target.sendPacket(SystemMsg.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
                attacker.sendPacket(new SystemMessage(SystemMsg.C1_RESISTED_C2S_MAGIC).addName(target).addName(attacker));
            }
            else
            {
                target.sendPacket(SystemMsg.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);
                attacker.sendPacket(new SystemMessage(SystemMsg.YOUR_OPPONENT_HAS_RESISTANCE_TO_MAGIC_THE_DAMAGE_WAS_DECREASED));
            }
        }

        int levelDiff = target.getLevel() - attacker.getLevel(); // C Gracia Epilogue уровень маг. атак считается только по уроню атакующего

        if (damage > 1)
        {
            if (skill.getTemplate().isDeathlink())
                damage *= 1.8 * (1.0 - attacker.getCurrentHpRatio());

            if(skill.getTemplate().isBasedOnTargetDebuff())
            {
                int effectCount = 0;
                for (Effect e : target.getEffectList().getAllFirstEffects())
                    if (!e.getSkill().getTemplate().isToggle())
                        effectCount++;

                damage *= 0.3 + 0.0875 * effectCount;
            }
        }

        damage += lethalDamage;

        if(isPvP && damage > 1)
        {
            damage *= attacker.calcStat(Stats.PVP_MAGIC_SKILL_DMG_BONUS, 1, null, null);
            damage /= target.calcStat(Stats.PVP_MAGIC_SKILL_DEFENCE_BONUS, 1, null, null);
        }

        double magic_rcpt = target.calcStat(Stats.MAGIC_RESIST, attacker, skill) - attacker.calcStat(Stats.MAGIC_POWER, target, skill);
        double lvlMod =  4. * Math.max(1., target.getLevel() >= 80 ? (levelDiff - 4) * 1.6 : (levelDiff - 14) * 2);
        double failChance = gradePenalty ? 95. : Math.min(lvlMod * (1. + magic_rcpt / 100.), 95.);
        double resistChance = gradePenalty ? 95. : 5 * Math.max(levelDiff - 10, 1);

        if (attacker.isPlayer() && ((Player)attacker).isDebug())
            attacker.sendMessage("Fail chance " + (int)failChance + "/" + (int)resistChance);

        if(Rnd.chance(failChance))
        {
            if(Rnd.chance(resistChance))
            {
                damage = 0;
                SystemMessage msg = new SystemMessage(SystemMsg.C1_RESISTED_C2S_MAGIC).addName(target).addName(attacker);
                attacker.sendPacket(msg);
                target.sendPacket(msg);
            }
            else
            {
                damage /= 2;
                SystemMessage msg = new SystemMessage(SystemMsg.DAMAGE_IS_DECREASED_BECAUSE_C1_RESISTED_C2S_MAGIC).addName(target).addName(attacker);
                attacker.sendPacket(msg);
                target.sendPacket(msg);
            }
        }

        attacker.displayGiveDamageMessage(target, (int) damage, crit, false, false, true);

        if(calcCastBreak(target, crit))
            target.abortCast(false, true);

        return damage;
    }

    public static void calcStunBreak(Creature target, boolean crit)
    {
        if (target.isStunned() && Rnd.chance(crit ? 75 : 10))
            for (Effect e : target.getEffectList().getAllEffects())
                if (e.getEffectType() == EffectType.Stun)
                {
                    e.exit();
                    if (target.isPlayer())
                        target.sendPacket(new SystemMessage(SystemMsg.THE_EFFECT_OF_S1_HAS_BEEN_REMOVED).addSkillName(e.getSkill()));
                }

        // TODO: DS: убрать хардкод
        if (Rnd.chance(crit ? 10 : 2)) // real_target
            for (Effect e : target.getEffectList().getAllEffects())
                if (e.getSkill().getId() == 522)
                {
                    e.exit();
                    if (target.isPlayer())
                        target.sendPacket(new SystemMessage(SystemMsg.THE_EFFECT_OF_S1_HAS_BEEN_REMOVED).addSkillName(e.getSkill()));
                }
    }

    /** Returns true in case of fatal blow success */
    public static boolean calcBlow(Creature activeChar, Creature target, SkillEntry skill)
    {
        WeaponTemplate weapon = activeChar.getActiveWeaponItem();

        double base_weapon_crit = weapon == null ? 4. : weapon.getCritical();
        double crit_height_bonus = 0.008 * Math.min(25, Math.max(-25, target.getZ() - activeChar.getZ())) + 1.1;
        double buffs_mult = activeChar.calcStat(Stats.FATALBLOW_RATE, target, skill);
        double skill_mod = skill.getTemplate().isBehind() ? 6 : 5; // CT 2.3 blowrate increase

        double chance = base_weapon_crit * buffs_mult * crit_height_bonus * skill_mod;

        if(!target.isInCombat())
            chance *= 1.1;

        switch(PositionUtils.getDirectionTo(target, activeChar))
        {
            case BEHIND:
                chance *= 1.3;
                break;
            case SIDE:
                chance *= 1.1;
                break;
            case FRONT:
                if(skill.getTemplate().isBehind())
                    chance = 3.0;
                break;
        }
        chance = Math.min(skill.getTemplate().isBehind() ? 100 : 80, chance);
        return Rnd.chance(chance);
    }

    /** Возвращает шанс крита в процентах */
    public static double calcCrit(Creature attacker, Creature target, SkillEntry skill, boolean blow)
    {
        if(attacker.isPlayer() && attacker.getActiveWeaponItem() == null)
            return 0;
        if(skill != null)
            return skill.getTemplate().getCriticalRate() * (blow ? BaseStats.DEX.calcBonus(attacker) : BaseStats.STR.calcBonus(attacker)) * 0.01 * attacker.calcStat(Stats.SKILL_CRIT_CHANCE_MOD, target, skill);

        double rate = attacker.getCriticalHit(target, null) * 0.01 * target.calcStat(Stats.CRIT_CHANCE_RECEPTIVE, attacker, skill);

        switch(PositionUtils.getDirectionTo(target, attacker))
        {
            case BEHIND:
                rate *= 1.4;
                break;
            case SIDE:
                rate *= 1.2;
                break;
        }

        return rate / 10;
    }

    public static boolean calcMCrit(double mRate)
    {
        // floating point random gives more accuracy calculation, because argument also floating point
        return Rnd.get() * 100 <= Math.min(Config.LIM_MCRIT, mRate);
    }

    public static boolean calcCastBreak(Creature target, boolean crit)
    {
        if(target == null || target.isDamageBlocked() || target.isRaid() || !target.isCastingNow())
            return false;
        SkillEntry skill = target.getCastingSkill();
        if (skill == null)
            return false;
        if(skill.getTemplate().getMagicType() == SkillMagicType.PHYSIC || skill.getTemplate().getMagicType() == SkillMagicType.MUSIC)//(skill.getSkillType() == SkillType.TAKECASTLE || skill.getSkillType() == SkillType.TAKEFORTRESS || skill.getSkillType() == SkillType.TAKEFLAG))
            return false;
        return Rnd.chance(target.calcStat(Stats.CAST_INTERRUPT, crit ? 75 : 10, null, skill));
    }

    /** Calculate delay (in milliseconds) before next ATTACK */
    public static int calcPAtkSpd(double rate)
    {
        return (int) (500000 / rate); // в миллисекундах поэтому 500*1000
    }

    /** Calculate delay (in milliseconds) for skills cast */
    public static int calcMAtkSpd(Creature attacker, Skill skill, double skillTime)
    {
        if(skill.isMagic())
            return (int) (skillTime * 333 / Math.max(attacker.getMAtkSpd(), 1));
        return (int) (skillTime * 333 / Math.max(attacker.getPAtkSpd(true), 1));
    }

    /** Calculate reuse delay (in milliseconds) for skills */
    public static long calcSkillReuseDelay(Creature actor, SkillEntry skill)
    {
        long reuseDelay = skill.getTemplate().getReuseDelay();
        if(actor.isMonster())
            reuseDelay = skill.getTemplate().getReuseForMonsters();
        if(skill.getTemplate().isReuseDelayPermanent() || skill.getTemplate().isHandler() || skill.getTemplate().isItemSkill())
            return reuseDelay;
        if(actor.getSkillMastery(skill.getTemplate()).hasZeroReuse())
            return 0;
        if (skill.getTemplate().isMusic())
            return (long) actor.calcStat(Stats.MUSIC_REUSE_RATE, reuseDelay, null, skill);
        if(skill.getTemplate().isMagic())
            return (long) actor.calcStat(Stats.MAGIC_REUSE_RATE, reuseDelay, null, skill);
        return (long) actor.calcStat(Stats.PHYSIC_REUSE_RATE, reuseDelay, null, skill);
    }

    /** Returns true if hit missed (target evaded) */
    public static boolean calcHitMiss(Creature attacker, Creature target)
    {
        int chanceToHit = 88 + 2 * (attacker.getAccuracy() - target.getEvasionRate(attacker));

        chanceToHit = Math.max(chanceToHit, 28);
        chanceToHit = Math.min(chanceToHit, 98);

        PositionUtils.TargetDirection direction = PositionUtils.getDirectionTo(attacker, target);
        switch(direction)
        {
            case BEHIND:
                chanceToHit *= 1.2;
                break;
            case SIDE:
                chanceToHit *= 1.1;
                break;
        }
        return !Rnd.chance(chanceToHit);
    }

    /** Returns true if shield defence successfull */
    public static boolean calcShldUse(Creature attacker, Creature target)
    {
        WeaponTemplate template = target.getSecondaryWeaponItem();
        if(template == null || template.getItemType() != WeaponTemplate.WeaponType.NONE)
            return false;
        int angle = (int) target.calcStat(Stats.SHIELD_ANGLE, attacker, null);
        if(!PositionUtils.isFacing(target, attacker, angle))
            return false;
        return Rnd.chance((int)target.calcStat(Stats.SHIELD_RATE, attacker, null));
    }

    public static boolean[] isDebugEnabled(Creature caster, Creature target)
    {
        if (Config.ALT_DEBUG_ENABLED)
        {
            // Включена ли отладка на кастере
            final boolean debugCaster = caster.getPlayer() != null && caster.getPlayer().isDebug();
            // Включена ли отладка на таргете
            final boolean debugTarget = target.getPlayer() != null && target.getPlayer().isDebug();
            // Разрешена ли отладка в PvP
            if (Config.ALT_DEBUG_PVP_ENABLED && (debugCaster && debugTarget) && (!Config.ALT_DEBUG_PVP_DUEL_ONLY || (caster.getPlayer().isInDuel() && target.getPlayer().isInDuel())))
                return new boolean[]{true, debugCaster, debugTarget};
            // Включаем отладку в PvE если разрешено
            if (Config.ALT_DEBUG_PVE_ENABLED && ((debugCaster && target.isMonster()) || (debugTarget && caster.isMonster())))
                return new boolean[]{true, debugCaster, debugTarget};
        }

        return DEBUG_DISABLED;
    }

    public static boolean calcSkillSuccess(Env env, EffectTemplate et, int spiritshot)
    {
        if(env.value == -1)
            return true;

        env.value = Math.max(Math.min(env.value, 150), 1); // На всякий случай
        final double base = env.value; // Запоминаем базовый шанс (нужен позже)

        final SkillEntry skill = env.skill;
        if(!skill.getTemplate().isOffensive())
            return Rnd.chance(env.value);

        final Creature caster = env.character;
        final Creature target = env.target;

        final boolean[] debug = isDebugEnabled(caster, target);
        final boolean debugGlobal = debug[0];
        final boolean debugCaster = debug[1];
        final boolean debugTarget = debug[2];

        double statMod = 1.;
        if(skill.getTemplate().getSaveVs() != null)
        {
            statMod = skill.getTemplate().getSaveVs().calcChanceMod(target);
            env.value *= statMod; // Бонус от MEN/CON/etc
        }

        env.value = Math.max(env.value, 1);

        double mAtkMod = 1.;
        int ssMod = 0;
        if(skill.getTemplate().isMagic() && (et == null || et.chance() < 0)) // Этот блок только для магических скиллов, эффекты с отдельным шансом тоже пропускаются
        {
            int mdef = Math.max(1, target.getMDef(target, skill)); // Вычисляем mDef цели
            double matk = caster.getMAtk(target, skill);

            if(skill.getTemplate().isSSPossible())
            {
                switch (spiritshot)
                {
                case ItemInstance.CHARGED_BLESSED_SPIRITSHOT:
                    ssMod = 4;
                    break;
                case ItemInstance.CHARGED_SPIRITSHOT:
                    ssMod = 2;
                    break;
                default:
                    ssMod = 1;
                }
                matk *= ssMod;
            }

            mAtkMod = Config.SKILLS_CHANCE_MOD * Math.pow(matk, Config.SKILLS_CHANCE_POW) / mdef;

            /*
            if (mAtkMod < 0.7)
                mAtkMod = 0.7;
            else if (mAtkMod > 1.4)
                mAtkMod = 1.4;
            */

            env.value *= mAtkMod;
            env.value = Math.max(env.value, 1);
        }

        double lvlDependMod = skill.getTemplate().getLevelModifier();
        if (lvlDependMod != 0)
        {
            final int attackLevel =  skill.getTemplate().getMagicLevel() > 0 ? skill.getTemplate().getMagicLevel() : caster.getLevel();
            /*final int delta = attackLevel - target.getLevel();
            lvlDependMod = delta / 5;
            lvlDependMod = lvlDependMod * 5;
            if (lvlDependMod != delta)
                lvlDependMod = delta < 0 ? lvlDependMod - 5 : lvlDependMod + 5;

            env.value += lvlDependMod;*/
            lvlDependMod = 1. + (attackLevel - target.getLevel()) * 0.03 * lvlDependMod;
            if (lvlDependMod < 0)
                lvlDependMod = 0;
            else if (lvlDependMod > 2)
                lvlDependMod = 2;

            env.value *= lvlDependMod;
        }

        double vulnMod = 0;
        double profMod = 0;
        double resMod = 1.;
        double debuffMod = 1.;
        if(!skill.getTemplate().isIgnoreResists())
        {
            if (et == null || et.chance() < 0) // Эффекты с индивидуальным шансом - не дебафы (сброс цели). TODO: отдельный флаг
            {
                debuffMod = 1. - (target.calcStat(Stats.DEBUFF_RESIST, 100., caster, skill) - 100.) / 120.;

                if(debuffMod != 1) // Внимание, знак был изменен на противоположный !
                {
                    if (debuffMod == Double.NEGATIVE_INFINITY)
                    {
                        if (debugGlobal)
                        {
                            if (debugCaster)
                                caster.getPlayer().sendMessage("Full debuff immunity");
                            if (debugTarget)
                                target.getPlayer().sendMessage("Full debuff immunity");
                        }
                        return false;
                    }
                    if (debuffMod == Double.POSITIVE_INFINITY)
                    {
                        if (debugGlobal)
                        {
                            if (debugCaster)
                                caster.getPlayer().sendMessage("Full debuff vulnerability");
                            if (debugTarget)
                                target.getPlayer().sendMessage("Full debuff vulnerability");
                        }
                        return true;
                    }

                    debuffMod = Math.max(debuffMod, 0);
                    env.value *= debuffMod;
                }
            }

            SkillTrait trait = skill.getTemplate().getTraitType();
            if (trait != null)
            {
                vulnMod = trait.calcVuln(env);
                profMod = trait.calcProf(env);

                final double maxResist = 90 + profMod * 0.85;
                resMod = (maxResist - vulnMod) / 60.;
            }

            if(resMod != 1) // Внимание, знак был изменен на противоположный !
            {
                if (resMod == Double.NEGATIVE_INFINITY)
                {
                    if (debugGlobal)
                    {
                        if (debugCaster)
                            caster.getPlayer().sendMessage("Full immunity");
                        if (debugTarget)
                            target.getPlayer().sendMessage("Full immunity");
                    }
                    return false;
                }
                if (resMod == Double.POSITIVE_INFINITY)
                {
                    if (debugGlobal)
                    {
                        if (debugCaster)
                            caster.getPlayer().sendMessage("Full vulnerability");
                        if (debugTarget)
                            target.getPlayer().sendMessage("Full vulnerability");
                    }
                    return true;
                }

                resMod = Math.max(resMod, 0);
                env.value *= resMod;
            }
        }

        double elementMod = 0;
        final Element element = skill.getTemplate().getElement();
        if (element != Element.NONE)
        {
            elementMod = skill.getTemplate().getElementPower();
            Element attackElement = getAttackElement(caster, target);
            if (attackElement == element)
                elementMod += caster.calcStat(element.getAttack(), 0, target, null);

            elementMod = getElementMod(elementMod, target.calcStat(element.getDefence(), 0, caster, null), caster.isPlayer() && target.isPlayer());
            env.value *= elementMod;
        }

        //if(skill.isSoulBoost()) // Бонус от душ камаелей
        //    env.value *= 0.85 + 0.06 * Math.min(character.getConsumedSouls(), 5);

        env.value = Math.max(env.value, Math.min(base, Config.SKILLS_CHANCE_MIN)); // Если базовый шанс более Config.SKILLS_CHANCE_MIN, то при небольшой разнице в уровнях, делаем кап снизу.
        env.value = Math.max(Math.min(env.value, Config.SKILLS_CHANCE_CAP), 1); // Применяем кап
        final boolean result = Rnd.chance((int)env.value);

        if (debugGlobal)
        {
            StringBuilder stat = new StringBuilder(100);
            stat.append(skill.getId());
            stat.append("/");              
            stat.append(skill.getDisplayLevel());
            stat.append(" ");              
            if (et == null)
                stat.append(skill.getTemplate().getName());
            else
                stat.append(et._effectType.name());
            stat.append(" AR:");
            stat.append((int)base);
            stat.append(" ");              
            if (skill.getTemplate().getSaveVs() != null)
            {
                stat.append(skill.getTemplate().getSaveVs().name());
                stat.append(":");              
                stat.append(String.format("%1.1f", statMod));
            }
            if (skill.getTemplate().isMagic())
            {
                stat.append(" ");              
                stat.append(" mAtk:");
                stat.append(String.format("%1.1f", mAtkMod));
                stat.append(" SS:");
                stat.append(ssMod);
            }
            if (skill.getTemplate().getTraitType() != null)
            {
                stat.append(" ");              
                stat.append(skill.getTemplate().getTraitType().name());
            }
            stat.append(" ");
            stat.append(String.format("%1.1f", resMod));
            stat.append("(");
            stat.append(String.format("%1.1f", profMod));
            stat.append("/");
            stat.append(String.format("%1.1f", vulnMod));
            if (debuffMod != 0)
            {
                stat.append("+");
                stat.append(String.format("%1.1f", debuffMod));
            }
            stat.append(") lvl:");
            stat.append(String.format("%1.1f", lvlDependMod));
            stat.append(" elem:");
            stat.append(String.format("%1.1f", elementMod));
            stat.append(" Chance:");
            stat.append(String.format("%1.1f", env.value));
            if (!result)
                stat.append(" failed");

            // отсылаем отладочные сообщения
            if (debugCaster)
                caster.getPlayer().sendMessage(stat.toString());
            if (debugTarget)
                target.getPlayer().sendMessage(stat.toString());
        }
        return result;
    }

    public static boolean calcSkillSuccess(Creature player, Creature target, SkillEntry skill, int activateRate)
    {
        Env env = new Env();
        env.character = player;
        env.target = target;
        env.skill = skill;
        env.value = activateRate;
        return calcSkillSuccess(env, null, player.getChargedSpiritShot(false));
    }

    public static void calcSkillMastery(SkillEntry skill, Creature activeChar)
    {
        if(skill.getTemplate().isHandler())
            return;

        //Skill id 330 for fighters, 331 for mages
        //Actually only GM can have 2 skill masteries, so let's make them more lucky ^^
        if((activeChar.getSkillLevel(331) > 0 && activeChar.calcStat(Stats.SKILL_MASTERY, activeChar.getINT(), null, skill) >= Rnd.get(5000)) || (activeChar.getSkillLevel(330) > 0 && activeChar.calcStat(Stats.SKILL_MASTERY, activeChar.getSTR(), null, skill) >= Rnd.get(5000)))
            activeChar.setSkillMastery(skill.getTemplate());
    }

    public static double calcDamageResists(SkillEntry skill, Creature attacker, Creature defender, double value)
    {
        if(attacker == defender) // это дамаг от местности вроде ожога в лаве, наносится от своего имени
            return value; // TODO: по хорошему надо учитывать защиту, но поскольку эти скиллы немагические то надо делать отдельный механизм

        if(attacker.isBoss())
            value *= Config.RATE_EPIC_ATTACK;
        else if(attacker.isRaid() || attacker instanceof ReflectionBossInstance)
            value *= Config.RATE_RAID_ATTACK;

        if(defender.isBoss())
            value /= Config.RATE_EPIC_DEFENSE;
        else if(defender.isRaid() || defender instanceof ReflectionBossInstance)
            value /= Config.RATE_RAID_DEFENSE;

        Player pAttacker = attacker.getPlayer();

        // если уровень игрока ниже чем на 2 и более уровней моба 78+, то его урон по мобу снижается
        int diff = defender.getLevel() - (pAttacker != null ? pAttacker.getLevel() : attacker.getLevel());
        if(attacker.isPlayable() && defender.isMonster() && defender.getLevel() >= 78 && diff > 2)
            value *= .7 / Math.pow(diff - 2, .25);

        Element element = Element.NONE;
        double power = 0.;

        // использует элемент умения
        if(skill != null)
        {
            element = skill.getTemplate().getElement();
            power = skill.getTemplate().getElementPower();
        }
        // используем максимально эффективный элемент
        else
            element = getAttackElement(attacker, defender);

        if(element == Element.NONE)
            return value;

        if(pAttacker != null && pAttacker.isGM() && Config.DEBUG)
        {
            pAttacker.sendMessage("Element: " + element.name());
            pAttacker.sendMessage("Attack: " + attacker.calcStat(element.getAttack(), power, defender, skill));
            pAttacker.sendMessage("Defence: " + defender.calcStat(element.getDefence(), 0., attacker, skill));
            pAttacker.sendMessage("Modifier: " + getElementMod(attacker.calcStat(element.getAttack(), power, defender, skill), defender.calcStat(element.getDefence(), 0., attacker, skill), attacker.isPlayer() && defender.isPlayer()));
        }

        return value * getElementMod(attacker.calcStat(element.getAttack(), power, defender, skill), defender.calcStat(element.getDefence(), 0., attacker, skill), attacker.isPlayer() && defender.isPlayer());
    }

    /**
     * Возвращает множитель для атаки из значений атакующего и защитного элемента.
     * <br /><br />
     * Диапазон от 1.0 до 1.7 (Freya)
     * <br /><br />
     * @param attack значение атаки
     * @param defense значение защиты
     * @param isPvP если оба игроки (самоны не считаются)
     * @return множитель
     */
    private static double getElementMod(double attack, double defense, boolean isPvP)
    {
        double diff = attack - defense;
        if(diff <= 0)
            return 1.0;
        else if(diff < 50)
            return 1.0 + diff * 0.003948;
        else if(diff < 150)
            return 1.2;
        else if(diff < 300)
            return 1.4;
        else
            return 1.7;
    }

    /**
     * Возвращает максимально эффективный атрибут, при атаке цели
     * @param attacker
     * @param target
     * @return
     */
    public static Element getAttackElement(Creature attacker, Creature target)
    {
        double val, max = Double.MIN_VALUE;
        Element result = Element.NONE;
        for(Element e : Element.VALUES)
        {
            val = attacker.calcStat(e.getAttack(), 0., target, null);
            if(val <= 0.)
                continue;

            if(target != null)
                val -= target.calcStat(e.getDefence(), 0., attacker, null);

            if(val > max)
            {
                result = e;
                max = val;
            }
        }

        return result;
    }
}
Шикарно) спасибо... я так вижу,что это нормальный вариант ;) Как всегда выручаешь,спасибо
 
У предметов типа арбалет и лук помимо параметра скорости атаки attack_speed, есть еще параметр reuse_delay, который задает задержку перед следующим выстрелом.
 
  • Мне нравится
Реакции: Caper111

    Caper111

    Баллов: 1
    За эпический ответ
Я его даже не трогал. Просто с лостов скинул пример.
Там все норм, но проблема не исчезла, по ходу как писали выше дело в задержке перед следующим выстрелом.

У предметов типа арбалет и лук помимо параметра скорости атаки attack_speed, есть еще параметр reuse_delay, который задает задержку перед следующим выстрелом.
<set name="reuse_delay" val="1300"/> задержка 1300 мс. Я так понимаю ставить теперь значение меньше и эксперементировать с добавлением скорости. ?

<set name="reuse_delay" val="1300"/> задержка 1300 мс. Я так понимаю ставить теперь значение меньше и эксперементировать с добавлением скорости. ?
Вы были правы, получилось пофиксить :)
 
Последнее редактирование модератором:
Назад
Сверху Снизу