если сборка овероподобная то в альтсеттинге конфиг естьОчень странное поведение персонажей с кастом 1500(в статистике перса)+ они бьют очень медленно,как будто что-то урезает их каст. (только Луки и Арбы,остальные норм) В конфиге смотрел, ограничение до 1999 скорость атаки, кто знает как решить? где копать? может в ядре? может в датапаке?
Можно для "тупых" что за альтсеттинг? сборка Годовесли сборка овероподобная то в альтсеттинге конфиг есть
а от чего тогда зависит ? разве не от скорости физ атаки?Скорость, с которой они бьют, не зависит от каста.
автоатака у луков зависит от Atk SpeedМожно для "тупых" что за альтсеттинг? сборка Годов
а от чего тогда зависит ? разве не от скорости физ атаки?
Скорость атаки и скорость каста это разные параметры. При использовании оружия типа 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;
}
}
Шикарно) спасибо... я так вижу,что это нормальный вариантНу наверное что-то типа этого:
Код: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; } }
Я его даже не трогал. Просто с лостов скинул пример.Шикарно) спасибо... я так вижу,что это нормальный вариантКак всегда выручаешь,спасибо
Там все норм, но проблема не исчезла, по ходу как писали выше дело в задержке перед следующим выстрелом.Я его даже не трогал. Просто с лостов скинул пример.
<set name="reuse_delay" val="1300"/> задержка 1300 мс. Я так понимаю ставить теперь значение меньше и эксперементировать с добавлением скорости. ?У предметов типа арбалет и лук помимо параметра скорости атаки attack_speed, есть еще параметр reuse_delay, который задает задержку перед следующим выстрелом.
Вы были правы, получилось пофиксить<set name="reuse_delay" val="1300"/> задержка 1300 мс. Я так понимаю ставить теперь значение меньше и эксперементировать с добавлением скорости. ?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?