diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9d48799cd6..deb1701ba3 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -4230,7 +4230,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe return getStat().getMagicEvasionRate(); } - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { return getStat().getAttackSpeedMultiplier(); } diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java index 756ee0146c..1a76172f79 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java @@ -44,6 +44,7 @@ import com.l2jmobius.gameserver.model.skills.AbnormalType; import com.l2jmobius.gameserver.model.skills.BuffInfo; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.skills.SkillConditionScope; +import com.l2jmobius.gameserver.model.stats.Formulas; import com.l2jmobius.gameserver.model.stats.MoveType; import com.l2jmobius.gameserver.model.stats.Stats; import com.l2jmobius.gameserver.model.stats.StatsHolder; @@ -76,6 +77,10 @@ public class CharStat private final Deque _additionalMul = new ConcurrentLinkedDeque<>(); private final Map _fixedValue = new ConcurrentHashMap<>(); + /** Values to be recalculated after every stat update */ + private double _attackSpeedMultiplier = 1; + private double _mAttackSpeedMultiplier = 1; + private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(); public CharStat(L2Character activeChar) @@ -109,9 +114,14 @@ public class CharStat /** * @return the Attack Speed multiplier (base+modifier) of the L2Character to get proper animations. */ - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { - return (float) (((1.1) * getPAtkSpd()) / _activeChar.getTemplate().getBasePAtkSpd()); + return _attackSpeedMultiplier; + } + + public final double getMAttackSpeedMultiplier() + { + return _mAttackSpeedMultiplier; } /** @@ -801,6 +811,9 @@ public class CharStat // Merge with additional stats _additionalAdd.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeAdd(holder.getStat(), holder.getValue())); _additionalMul.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeMul(holder.getStat(), holder.getValue())); + + _attackSpeedMultiplier = Formulas.calcAtkSpdMultiplier(_activeChar); + _mAttackSpeedMultiplier = Formulas.calcMAtkSpdMultiplier(_activeChar); } finally { diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java index 7083d575af..d98d83bb9b 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java @@ -100,6 +100,8 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable private int _spiritShotChance; private int _minSkillChance; private int _maxSkillChance; + private double _hitTimeFactor; + private double _hitTimeFactorSkill; private Map _skills; private Map> _aiSkillLists; private Set _clans; @@ -184,6 +186,9 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable _minSkillChance = set.getInt("minSkillChance", 7); _maxSkillChance = set.getInt("maxSkillChance", 15); + _hitTimeFactor = set.getInt("hit_time", 100) / 100d; + _hitTimeFactorSkill = set.getInt("hit_time_skill", 100) / 100d; + _collisionRadiusGrown = set.getDouble("collisionRadiusGrown", 0); _collisionHeightGrown = set.getDouble("collisionHeightGrown", 0); @@ -502,6 +507,16 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable return _maxSkillChance; } + public double getHitTimeFactor() + { + return _hitTimeFactor; + } + + public double getHitTimeFactorSkill() + { + return _hitTimeFactorSkill; + } + @Override public Map getSkills() { diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java index 068361251b..58a6ee155c 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java @@ -92,13 +92,14 @@ public class SkillCaster implements Runnable private final Skill _skill; private final L2ItemInstance _item; private final SkillCastingType _castingType; - private final int _castTime; + private int _hitTime; + private int _cancelTime; private int _coolTime; private Collection _targets; private ScheduledFuture _task; private int _phase; - private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed, int castTime) + private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed) { Objects.requireNonNull(caster); Objects.requireNonNull(skill); @@ -109,7 +110,8 @@ public class SkillCaster implements Runnable _skill = skill; _item = item; _castingType = castingType; - _castTime = castTime; + + calcSkillTiming(caster, skill); } /** @@ -171,10 +173,8 @@ public class SkillCaster implements Runnable return null; } - castTime = castTime > -1 ? castTime : Formulas.calcHitTime(caster, skill); - // Schedule a thread that will execute 500ms before casting time is over (for animation issues and retail handling). - final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed, castTime); + final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed); skillCaster.run(); return skillCaster; } @@ -198,13 +198,13 @@ public class SkillCaster implements Runnable case 0: // Start skill casting. { hasNextPhase = startCasting(); - nextTaskDelay = _castTime; + nextTaskDelay = _hitTime; break; } case 1: // Launch the skill. { hasNextPhase = launchSkill(); - nextTaskDelay = Formulas.SKILL_LAUNCH_TIME; + nextTaskDelay = _cancelTime; break; } case 2: // Finish launching and apply effects. @@ -238,7 +238,7 @@ public class SkillCaster implements Runnable } _coolTime = Formulas.calcAtkSpd(caster, _skill, _skill.getCoolTime()); // TODO Get proper formula of this. - final int displayedCastTime = _castTime + Formulas.SKILL_LAUNCH_TIME; // For client purposes, it must be displayed to player the skill casting time + launch time. + final int displayedCastTime = _hitTime + _cancelTime; // For client purposes, it must be displayed to player the skill casting time + launch time. final boolean instantCast = (_castingType == SkillCastingType.SIMULTANEOUS) || _skill.isAbnormalInstant() || _skill.isWithoutAction(); // Add this SkillCaster to the creature so it can be marked as casting. @@ -762,6 +762,23 @@ public class SkillCaster implements Runnable } } + private void calcSkillTiming(L2Character creature, Skill skill) + { + final double timeFactor = Formulas.calcSkillTimeFactor(creature, skill); + final double cancelTime = Formulas.calcSkillCancelTime(creature, skill); + if (skill.getOperateType().isChanneling()) + { + _hitTime = (int) Math.max(skill.getHitTime() - cancelTime, 0); + _cancelTime = 2866; + } + else + { + _hitTime = (int) Math.max((skill.getHitTime() / timeFactor) - cancelTime, 0); + _cancelTime = (int) cancelTime; + } + _coolTime = (int) (skill.getCoolTime() / timeFactor); // cooltimeMillis / timeFactor + } + public static void triggerCast(L2Character activeChar, L2Character target, Skill skill) { triggerCast(activeChar, target, skill, null, true); diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java index d807bcdfd7..b6cc5a08c7 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -30,6 +30,7 @@ import com.l2jmobius.gameserver.enums.DispelSlotType; import com.l2jmobius.gameserver.enums.Position; import com.l2jmobius.gameserver.enums.ShotType; import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance; import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance; @@ -427,80 +428,57 @@ public final class Formulas return (int) ((skillTime / attacker.getPAtkSpd()) * 300); } - /** - * TODO: Implement those: - *
    - *
  • Skill cool time is block player from doing anything (moving, casting, attacking).
  • - *
  • Seems hardcoded channeling value is not used for the skill task
  • - *
- * @param creature - * @param skill - * @return the hit time of the skill. - */ - public static int calcHitTime(L2Character creature, Skill skill) + public static double calcAtkSpdMultiplier(L2Character creature) { - int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME; - - // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd). - if (!skill.isStatic()) - { - skillTime = calcAtkSpd(creature, skill, skillTime); - } - // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS) - if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS))) - { - skillTime = (int) (0.6 * skillTime); - } - - return Math.max(skillTime, 0); + double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! + double dexBonus = BaseStats.DEX.calcBonus(creature); + double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 + double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); + double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); + return (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); + } + + public static double calcMAtkSpdMultiplier(L2Character creature) + { + final double armorBonus = 1; // TODO: Implement me! + final double witBonus = BaseStats.WIT.calcBonus(creature); + final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); + final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); + return ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); } /** - * TODO: Implement armor bonus and NPC Divider * @param creature * @param skill - * @return + * @return factor divisor for skill hit time and cancel time. */ public static double calcSkillTimeFactor(L2Character creature, Skill skill) { - double factor = 0; - if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3 + if (skill.getOperateType().isChanneling() || (skill.getMagicType() == 2) || (skill.getMagicType() == 4) || (skill.getMagicType() == 21)) { - final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! - final double dexBonus = BaseStats.DEX.calcBonus(creature); - final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 - final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); - final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); - factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); - } - else if (skill.isMagic()) // is_magic = 1 - { - final double armorBonus = 1; // TODO: Implement me! - final double witBonus = BaseStats.WIT.calcBonus(creature); - final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed - final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); - factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); - } - else if (skill.isStatic()) // is_magic = 2 - { - factor = 1; + return 1.0d; } - if (skill.isChanneling()) // operate type = 5 or 6 or 7 + double factor = 0.0; + if (skill.getMagicType() == 1) { - factor = 1; + final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values + factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time) + } + else + { + factor = creature.getAttackSpeedMultiplier(); } - if (creature.isNpc() || creature.isSummon()) + if (creature.isNpc()) { - // TODO: Implement me! - // if (attacker.unk08B0 > 0) + double npcFactor = ((L2Npc) creature).getTemplate().getHitTimeFactorSkill(); + if (npcFactor > 0) { - // factor /= attacker.unk08B0; + factor /= npcFactor; } } - - return Math.max(factor, 0.01); + return Math.max(0.01, factor); } public static double calcSkillCancelTime(L2Character creature, Skill skill) diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java index ba5ba7e725..879c53a403 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java @@ -62,23 +62,23 @@ public interface IStatsFunction default double calcWeaponBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElseGet(() -> + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElseGet(() -> { if (creature.isPet()) { final L2PetInstance pet = (L2PetInstance) creature; final L2ItemInstance weapon = pet.getActiveWeaponInstance(); - final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateBalue; + final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateValue; return baseVal + (weapon != null ? weapon.getItem().getStats(stat, baseVal) : 0); } else if (creature.isPlayer()) { final L2ItemInstance weapon = creature.getActiveWeaponInstance(); - return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateBalue) : baseTemplateBalue); + return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateValue) : baseTemplateValue); } - return baseTemplateBalue; + return baseTemplateValue; }); return baseValue; @@ -86,8 +86,8 @@ public interface IStatsFunction default double calcWeaponPlusBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElse(baseTemplateBalue); + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElse(baseTemplateValue); if (creature.isPlayable()) { diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java index 91907ac910..97d42c547e 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java @@ -87,7 +87,7 @@ public class CharInfo implements IClientOutgoingPacket _heading = _activeChar.getHeading(); _mAtkSpd = _activeChar.getMAtkSpd(); _pAtkSpd = _activeChar.getPAtkSpd(); - _attackSpeedMultiplier = _activeChar.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) _activeChar.getAttackSpeedMultiplier(); _moveMultiplier = cha.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(cha.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(cha.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java index 8e53e21a1d..e9ec1cfcd3 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java @@ -281,7 +281,7 @@ public class ExPetInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java index 9ba3b311b6..f3e272eb1c 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java @@ -58,7 +58,7 @@ public class FakePlayerInfo implements IClientOutgoingPacket _heading = npc.getHeading(); _mAtkSpd = npc.getMAtkSpd(); _pAtkSpd = npc.getPAtkSpd(); - _attackSpeedMultiplier = npc.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) npc.getAttackSpeedMultiplier(); _moveMultiplier = npc.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(npc.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(npc.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java index c294edab2a..60cd3a09a5 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java @@ -314,7 +314,7 @@ public class NpcInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _npc.getStat().getMovementSpeedMultiplier()); - packet.writeE(_npc.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _npc.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java index 99d3a634f8..a3d9499906 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java @@ -282,7 +282,7 @@ public class SummonInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9d48799cd6..deb1701ba3 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -4230,7 +4230,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe return getStat().getMagicEvasionRate(); } - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { return getStat().getAttackSpeedMultiplier(); } diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java index 756ee0146c..1a76172f79 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java @@ -44,6 +44,7 @@ import com.l2jmobius.gameserver.model.skills.AbnormalType; import com.l2jmobius.gameserver.model.skills.BuffInfo; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.skills.SkillConditionScope; +import com.l2jmobius.gameserver.model.stats.Formulas; import com.l2jmobius.gameserver.model.stats.MoveType; import com.l2jmobius.gameserver.model.stats.Stats; import com.l2jmobius.gameserver.model.stats.StatsHolder; @@ -76,6 +77,10 @@ public class CharStat private final Deque _additionalMul = new ConcurrentLinkedDeque<>(); private final Map _fixedValue = new ConcurrentHashMap<>(); + /** Values to be recalculated after every stat update */ + private double _attackSpeedMultiplier = 1; + private double _mAttackSpeedMultiplier = 1; + private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(); public CharStat(L2Character activeChar) @@ -109,9 +114,14 @@ public class CharStat /** * @return the Attack Speed multiplier (base+modifier) of the L2Character to get proper animations. */ - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { - return (float) (((1.1) * getPAtkSpd()) / _activeChar.getTemplate().getBasePAtkSpd()); + return _attackSpeedMultiplier; + } + + public final double getMAttackSpeedMultiplier() + { + return _mAttackSpeedMultiplier; } /** @@ -801,6 +811,9 @@ public class CharStat // Merge with additional stats _additionalAdd.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeAdd(holder.getStat(), holder.getValue())); _additionalMul.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeMul(holder.getStat(), holder.getValue())); + + _attackSpeedMultiplier = Formulas.calcAtkSpdMultiplier(_activeChar); + _mAttackSpeedMultiplier = Formulas.calcMAtkSpdMultiplier(_activeChar); } finally { diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java index 7083d575af..d98d83bb9b 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java @@ -100,6 +100,8 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable private int _spiritShotChance; private int _minSkillChance; private int _maxSkillChance; + private double _hitTimeFactor; + private double _hitTimeFactorSkill; private Map _skills; private Map> _aiSkillLists; private Set _clans; @@ -184,6 +186,9 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable _minSkillChance = set.getInt("minSkillChance", 7); _maxSkillChance = set.getInt("maxSkillChance", 15); + _hitTimeFactor = set.getInt("hit_time", 100) / 100d; + _hitTimeFactorSkill = set.getInt("hit_time_skill", 100) / 100d; + _collisionRadiusGrown = set.getDouble("collisionRadiusGrown", 0); _collisionHeightGrown = set.getDouble("collisionHeightGrown", 0); @@ -502,6 +507,16 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable return _maxSkillChance; } + public double getHitTimeFactor() + { + return _hitTimeFactor; + } + + public double getHitTimeFactorSkill() + { + return _hitTimeFactorSkill; + } + @Override public Map getSkills() { diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java index 068361251b..58a6ee155c 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java @@ -92,13 +92,14 @@ public class SkillCaster implements Runnable private final Skill _skill; private final L2ItemInstance _item; private final SkillCastingType _castingType; - private final int _castTime; + private int _hitTime; + private int _cancelTime; private int _coolTime; private Collection _targets; private ScheduledFuture _task; private int _phase; - private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed, int castTime) + private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed) { Objects.requireNonNull(caster); Objects.requireNonNull(skill); @@ -109,7 +110,8 @@ public class SkillCaster implements Runnable _skill = skill; _item = item; _castingType = castingType; - _castTime = castTime; + + calcSkillTiming(caster, skill); } /** @@ -171,10 +173,8 @@ public class SkillCaster implements Runnable return null; } - castTime = castTime > -1 ? castTime : Formulas.calcHitTime(caster, skill); - // Schedule a thread that will execute 500ms before casting time is over (for animation issues and retail handling). - final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed, castTime); + final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed); skillCaster.run(); return skillCaster; } @@ -198,13 +198,13 @@ public class SkillCaster implements Runnable case 0: // Start skill casting. { hasNextPhase = startCasting(); - nextTaskDelay = _castTime; + nextTaskDelay = _hitTime; break; } case 1: // Launch the skill. { hasNextPhase = launchSkill(); - nextTaskDelay = Formulas.SKILL_LAUNCH_TIME; + nextTaskDelay = _cancelTime; break; } case 2: // Finish launching and apply effects. @@ -238,7 +238,7 @@ public class SkillCaster implements Runnable } _coolTime = Formulas.calcAtkSpd(caster, _skill, _skill.getCoolTime()); // TODO Get proper formula of this. - final int displayedCastTime = _castTime + Formulas.SKILL_LAUNCH_TIME; // For client purposes, it must be displayed to player the skill casting time + launch time. + final int displayedCastTime = _hitTime + _cancelTime; // For client purposes, it must be displayed to player the skill casting time + launch time. final boolean instantCast = (_castingType == SkillCastingType.SIMULTANEOUS) || _skill.isAbnormalInstant() || _skill.isWithoutAction(); // Add this SkillCaster to the creature so it can be marked as casting. @@ -762,6 +762,23 @@ public class SkillCaster implements Runnable } } + private void calcSkillTiming(L2Character creature, Skill skill) + { + final double timeFactor = Formulas.calcSkillTimeFactor(creature, skill); + final double cancelTime = Formulas.calcSkillCancelTime(creature, skill); + if (skill.getOperateType().isChanneling()) + { + _hitTime = (int) Math.max(skill.getHitTime() - cancelTime, 0); + _cancelTime = 2866; + } + else + { + _hitTime = (int) Math.max((skill.getHitTime() / timeFactor) - cancelTime, 0); + _cancelTime = (int) cancelTime; + } + _coolTime = (int) (skill.getCoolTime() / timeFactor); // cooltimeMillis / timeFactor + } + public static void triggerCast(L2Character activeChar, L2Character target, Skill skill) { triggerCast(activeChar, target, skill, null, true); diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java index d807bcdfd7..b6cc5a08c7 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -30,6 +30,7 @@ import com.l2jmobius.gameserver.enums.DispelSlotType; import com.l2jmobius.gameserver.enums.Position; import com.l2jmobius.gameserver.enums.ShotType; import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance; import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance; @@ -427,80 +428,57 @@ public final class Formulas return (int) ((skillTime / attacker.getPAtkSpd()) * 300); } - /** - * TODO: Implement those: - *
    - *
  • Skill cool time is block player from doing anything (moving, casting, attacking).
  • - *
  • Seems hardcoded channeling value is not used for the skill task
  • - *
- * @param creature - * @param skill - * @return the hit time of the skill. - */ - public static int calcHitTime(L2Character creature, Skill skill) + public static double calcAtkSpdMultiplier(L2Character creature) { - int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME; - - // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd). - if (!skill.isStatic()) - { - skillTime = calcAtkSpd(creature, skill, skillTime); - } - // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS) - if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS))) - { - skillTime = (int) (0.6 * skillTime); - } - - return Math.max(skillTime, 0); + double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! + double dexBonus = BaseStats.DEX.calcBonus(creature); + double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 + double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); + double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); + return (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); + } + + public static double calcMAtkSpdMultiplier(L2Character creature) + { + final double armorBonus = 1; // TODO: Implement me! + final double witBonus = BaseStats.WIT.calcBonus(creature); + final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); + final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); + return ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); } /** - * TODO: Implement armor bonus and NPC Divider * @param creature * @param skill - * @return + * @return factor divisor for skill hit time and cancel time. */ public static double calcSkillTimeFactor(L2Character creature, Skill skill) { - double factor = 0; - if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3 + if (skill.getOperateType().isChanneling() || (skill.getMagicType() == 2) || (skill.getMagicType() == 4) || (skill.getMagicType() == 21)) { - final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! - final double dexBonus = BaseStats.DEX.calcBonus(creature); - final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 - final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); - final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); - factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); - } - else if (skill.isMagic()) // is_magic = 1 - { - final double armorBonus = 1; // TODO: Implement me! - final double witBonus = BaseStats.WIT.calcBonus(creature); - final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed - final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); - factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); - } - else if (skill.isStatic()) // is_magic = 2 - { - factor = 1; + return 1.0d; } - if (skill.isChanneling()) // operate type = 5 or 6 or 7 + double factor = 0.0; + if (skill.getMagicType() == 1) { - factor = 1; + final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values + factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time) + } + else + { + factor = creature.getAttackSpeedMultiplier(); } - if (creature.isNpc() || creature.isSummon()) + if (creature.isNpc()) { - // TODO: Implement me! - // if (attacker.unk08B0 > 0) + double npcFactor = ((L2Npc) creature).getTemplate().getHitTimeFactorSkill(); + if (npcFactor > 0) { - // factor /= attacker.unk08B0; + factor /= npcFactor; } } - - return Math.max(factor, 0.01); + return Math.max(0.01, factor); } public static double calcSkillCancelTime(L2Character creature, Skill skill) diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java index ba5ba7e725..879c53a403 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java @@ -62,23 +62,23 @@ public interface IStatsFunction default double calcWeaponBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElseGet(() -> + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElseGet(() -> { if (creature.isPet()) { final L2PetInstance pet = (L2PetInstance) creature; final L2ItemInstance weapon = pet.getActiveWeaponInstance(); - final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateBalue; + final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateValue; return baseVal + (weapon != null ? weapon.getItem().getStats(stat, baseVal) : 0); } else if (creature.isPlayer()) { final L2ItemInstance weapon = creature.getActiveWeaponInstance(); - return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateBalue) : baseTemplateBalue); + return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateValue) : baseTemplateValue); } - return baseTemplateBalue; + return baseTemplateValue; }); return baseValue; @@ -86,8 +86,8 @@ public interface IStatsFunction default double calcWeaponPlusBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElse(baseTemplateBalue); + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElse(baseTemplateValue); if (creature.isPlayable()) { diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java index 162359aca0..52978abea7 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java @@ -87,7 +87,7 @@ public class CharInfo implements IClientOutgoingPacket _heading = _activeChar.getHeading(); _mAtkSpd = _activeChar.getMAtkSpd(); _pAtkSpd = _activeChar.getPAtkSpd(); - _attackSpeedMultiplier = _activeChar.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) _activeChar.getAttackSpeedMultiplier(); _moveMultiplier = cha.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(cha.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(cha.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java index 8e53e21a1d..e9ec1cfcd3 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java @@ -281,7 +281,7 @@ public class ExPetInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java index 2f040f5e21..64ac2bc55c 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java @@ -58,7 +58,7 @@ public class FakePlayerInfo implements IClientOutgoingPacket _heading = npc.getHeading(); _mAtkSpd = npc.getMAtkSpd(); _pAtkSpd = npc.getPAtkSpd(); - _attackSpeedMultiplier = npc.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) npc.getAttackSpeedMultiplier(); _moveMultiplier = npc.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(npc.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(npc.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java index c294edab2a..60cd3a09a5 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java @@ -314,7 +314,7 @@ public class NpcInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _npc.getStat().getMovementSpeedMultiplier()); - packet.writeE(_npc.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _npc.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java index 99d3a634f8..a3d9499906 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java @@ -282,7 +282,7 @@ public class SummonInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9d48799cd6..deb1701ba3 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -4230,7 +4230,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe return getStat().getMagicEvasionRate(); } - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { return getStat().getAttackSpeedMultiplier(); } diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java index 756ee0146c..1a76172f79 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java @@ -44,6 +44,7 @@ import com.l2jmobius.gameserver.model.skills.AbnormalType; import com.l2jmobius.gameserver.model.skills.BuffInfo; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.skills.SkillConditionScope; +import com.l2jmobius.gameserver.model.stats.Formulas; import com.l2jmobius.gameserver.model.stats.MoveType; import com.l2jmobius.gameserver.model.stats.Stats; import com.l2jmobius.gameserver.model.stats.StatsHolder; @@ -76,6 +77,10 @@ public class CharStat private final Deque _additionalMul = new ConcurrentLinkedDeque<>(); private final Map _fixedValue = new ConcurrentHashMap<>(); + /** Values to be recalculated after every stat update */ + private double _attackSpeedMultiplier = 1; + private double _mAttackSpeedMultiplier = 1; + private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(); public CharStat(L2Character activeChar) @@ -109,9 +114,14 @@ public class CharStat /** * @return the Attack Speed multiplier (base+modifier) of the L2Character to get proper animations. */ - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { - return (float) (((1.1) * getPAtkSpd()) / _activeChar.getTemplate().getBasePAtkSpd()); + return _attackSpeedMultiplier; + } + + public final double getMAttackSpeedMultiplier() + { + return _mAttackSpeedMultiplier; } /** @@ -801,6 +811,9 @@ public class CharStat // Merge with additional stats _additionalAdd.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeAdd(holder.getStat(), holder.getValue())); _additionalMul.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeMul(holder.getStat(), holder.getValue())); + + _attackSpeedMultiplier = Formulas.calcAtkSpdMultiplier(_activeChar); + _mAttackSpeedMultiplier = Formulas.calcMAtkSpdMultiplier(_activeChar); } finally { diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java index 7083d575af..d98d83bb9b 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java @@ -100,6 +100,8 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable private int _spiritShotChance; private int _minSkillChance; private int _maxSkillChance; + private double _hitTimeFactor; + private double _hitTimeFactorSkill; private Map _skills; private Map> _aiSkillLists; private Set _clans; @@ -184,6 +186,9 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable _minSkillChance = set.getInt("minSkillChance", 7); _maxSkillChance = set.getInt("maxSkillChance", 15); + _hitTimeFactor = set.getInt("hit_time", 100) / 100d; + _hitTimeFactorSkill = set.getInt("hit_time_skill", 100) / 100d; + _collisionRadiusGrown = set.getDouble("collisionRadiusGrown", 0); _collisionHeightGrown = set.getDouble("collisionHeightGrown", 0); @@ -502,6 +507,16 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable return _maxSkillChance; } + public double getHitTimeFactor() + { + return _hitTimeFactor; + } + + public double getHitTimeFactorSkill() + { + return _hitTimeFactorSkill; + } + @Override public Map getSkills() { diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java index 068361251b..58a6ee155c 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java @@ -92,13 +92,14 @@ public class SkillCaster implements Runnable private final Skill _skill; private final L2ItemInstance _item; private final SkillCastingType _castingType; - private final int _castTime; + private int _hitTime; + private int _cancelTime; private int _coolTime; private Collection _targets; private ScheduledFuture _task; private int _phase; - private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed, int castTime) + private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed) { Objects.requireNonNull(caster); Objects.requireNonNull(skill); @@ -109,7 +110,8 @@ public class SkillCaster implements Runnable _skill = skill; _item = item; _castingType = castingType; - _castTime = castTime; + + calcSkillTiming(caster, skill); } /** @@ -171,10 +173,8 @@ public class SkillCaster implements Runnable return null; } - castTime = castTime > -1 ? castTime : Formulas.calcHitTime(caster, skill); - // Schedule a thread that will execute 500ms before casting time is over (for animation issues and retail handling). - final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed, castTime); + final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed); skillCaster.run(); return skillCaster; } @@ -198,13 +198,13 @@ public class SkillCaster implements Runnable case 0: // Start skill casting. { hasNextPhase = startCasting(); - nextTaskDelay = _castTime; + nextTaskDelay = _hitTime; break; } case 1: // Launch the skill. { hasNextPhase = launchSkill(); - nextTaskDelay = Formulas.SKILL_LAUNCH_TIME; + nextTaskDelay = _cancelTime; break; } case 2: // Finish launching and apply effects. @@ -238,7 +238,7 @@ public class SkillCaster implements Runnable } _coolTime = Formulas.calcAtkSpd(caster, _skill, _skill.getCoolTime()); // TODO Get proper formula of this. - final int displayedCastTime = _castTime + Formulas.SKILL_LAUNCH_TIME; // For client purposes, it must be displayed to player the skill casting time + launch time. + final int displayedCastTime = _hitTime + _cancelTime; // For client purposes, it must be displayed to player the skill casting time + launch time. final boolean instantCast = (_castingType == SkillCastingType.SIMULTANEOUS) || _skill.isAbnormalInstant() || _skill.isWithoutAction(); // Add this SkillCaster to the creature so it can be marked as casting. @@ -762,6 +762,23 @@ public class SkillCaster implements Runnable } } + private void calcSkillTiming(L2Character creature, Skill skill) + { + final double timeFactor = Formulas.calcSkillTimeFactor(creature, skill); + final double cancelTime = Formulas.calcSkillCancelTime(creature, skill); + if (skill.getOperateType().isChanneling()) + { + _hitTime = (int) Math.max(skill.getHitTime() - cancelTime, 0); + _cancelTime = 2866; + } + else + { + _hitTime = (int) Math.max((skill.getHitTime() / timeFactor) - cancelTime, 0); + _cancelTime = (int) cancelTime; + } + _coolTime = (int) (skill.getCoolTime() / timeFactor); // cooltimeMillis / timeFactor + } + public static void triggerCast(L2Character activeChar, L2Character target, Skill skill) { triggerCast(activeChar, target, skill, null, true); diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java index d807bcdfd7..b6cc5a08c7 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -30,6 +30,7 @@ import com.l2jmobius.gameserver.enums.DispelSlotType; import com.l2jmobius.gameserver.enums.Position; import com.l2jmobius.gameserver.enums.ShotType; import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance; import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance; @@ -427,80 +428,57 @@ public final class Formulas return (int) ((skillTime / attacker.getPAtkSpd()) * 300); } - /** - * TODO: Implement those: - *
    - *
  • Skill cool time is block player from doing anything (moving, casting, attacking).
  • - *
  • Seems hardcoded channeling value is not used for the skill task
  • - *
- * @param creature - * @param skill - * @return the hit time of the skill. - */ - public static int calcHitTime(L2Character creature, Skill skill) + public static double calcAtkSpdMultiplier(L2Character creature) { - int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME; - - // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd). - if (!skill.isStatic()) - { - skillTime = calcAtkSpd(creature, skill, skillTime); - } - // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS) - if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS))) - { - skillTime = (int) (0.6 * skillTime); - } - - return Math.max(skillTime, 0); + double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! + double dexBonus = BaseStats.DEX.calcBonus(creature); + double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 + double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); + double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); + return (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); + } + + public static double calcMAtkSpdMultiplier(L2Character creature) + { + final double armorBonus = 1; // TODO: Implement me! + final double witBonus = BaseStats.WIT.calcBonus(creature); + final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); + final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); + return ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); } /** - * TODO: Implement armor bonus and NPC Divider * @param creature * @param skill - * @return + * @return factor divisor for skill hit time and cancel time. */ public static double calcSkillTimeFactor(L2Character creature, Skill skill) { - double factor = 0; - if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3 + if (skill.getOperateType().isChanneling() || (skill.getMagicType() == 2) || (skill.getMagicType() == 4) || (skill.getMagicType() == 21)) { - final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! - final double dexBonus = BaseStats.DEX.calcBonus(creature); - final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 - final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); - final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); - factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); - } - else if (skill.isMagic()) // is_magic = 1 - { - final double armorBonus = 1; // TODO: Implement me! - final double witBonus = BaseStats.WIT.calcBonus(creature); - final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed - final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); - factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); - } - else if (skill.isStatic()) // is_magic = 2 - { - factor = 1; + return 1.0d; } - if (skill.isChanneling()) // operate type = 5 or 6 or 7 + double factor = 0.0; + if (skill.getMagicType() == 1) { - factor = 1; + final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values + factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time) + } + else + { + factor = creature.getAttackSpeedMultiplier(); } - if (creature.isNpc() || creature.isSummon()) + if (creature.isNpc()) { - // TODO: Implement me! - // if (attacker.unk08B0 > 0) + double npcFactor = ((L2Npc) creature).getTemplate().getHitTimeFactorSkill(); + if (npcFactor > 0) { - // factor /= attacker.unk08B0; + factor /= npcFactor; } } - - return Math.max(factor, 0.01); + return Math.max(0.01, factor); } public static double calcSkillCancelTime(L2Character creature, Skill skill) diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java index ba5ba7e725..879c53a403 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java @@ -62,23 +62,23 @@ public interface IStatsFunction default double calcWeaponBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElseGet(() -> + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElseGet(() -> { if (creature.isPet()) { final L2PetInstance pet = (L2PetInstance) creature; final L2ItemInstance weapon = pet.getActiveWeaponInstance(); - final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateBalue; + final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateValue; return baseVal + (weapon != null ? weapon.getItem().getStats(stat, baseVal) : 0); } else if (creature.isPlayer()) { final L2ItemInstance weapon = creature.getActiveWeaponInstance(); - return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateBalue) : baseTemplateBalue); + return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateValue) : baseTemplateValue); } - return baseTemplateBalue; + return baseTemplateValue; }); return baseValue; @@ -86,8 +86,8 @@ public interface IStatsFunction default double calcWeaponPlusBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElse(baseTemplateBalue); + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElse(baseTemplateValue); if (creature.isPlayable()) { diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java index 162359aca0..52978abea7 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java @@ -87,7 +87,7 @@ public class CharInfo implements IClientOutgoingPacket _heading = _activeChar.getHeading(); _mAtkSpd = _activeChar.getMAtkSpd(); _pAtkSpd = _activeChar.getPAtkSpd(); - _attackSpeedMultiplier = _activeChar.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) _activeChar.getAttackSpeedMultiplier(); _moveMultiplier = cha.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(cha.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(cha.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java index 8e53e21a1d..e9ec1cfcd3 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java @@ -281,7 +281,7 @@ public class ExPetInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java index 2f040f5e21..64ac2bc55c 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java @@ -58,7 +58,7 @@ public class FakePlayerInfo implements IClientOutgoingPacket _heading = npc.getHeading(); _mAtkSpd = npc.getMAtkSpd(); _pAtkSpd = npc.getPAtkSpd(); - _attackSpeedMultiplier = npc.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) npc.getAttackSpeedMultiplier(); _moveMultiplier = npc.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(npc.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(npc.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java index c294edab2a..60cd3a09a5 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java @@ -314,7 +314,7 @@ public class NpcInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _npc.getStat().getMovementSpeedMultiplier()); - packet.writeE(_npc.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _npc.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java index 9fbb4c1289..e62cba55ba 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java @@ -282,7 +282,7 @@ public class SummonInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9d48799cd6..deb1701ba3 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -4230,7 +4230,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe return getStat().getMagicEvasionRate(); } - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { return getStat().getAttackSpeedMultiplier(); } diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java index 756ee0146c..1a76172f79 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java @@ -44,6 +44,7 @@ import com.l2jmobius.gameserver.model.skills.AbnormalType; import com.l2jmobius.gameserver.model.skills.BuffInfo; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.skills.SkillConditionScope; +import com.l2jmobius.gameserver.model.stats.Formulas; import com.l2jmobius.gameserver.model.stats.MoveType; import com.l2jmobius.gameserver.model.stats.Stats; import com.l2jmobius.gameserver.model.stats.StatsHolder; @@ -76,6 +77,10 @@ public class CharStat private final Deque _additionalMul = new ConcurrentLinkedDeque<>(); private final Map _fixedValue = new ConcurrentHashMap<>(); + /** Values to be recalculated after every stat update */ + private double _attackSpeedMultiplier = 1; + private double _mAttackSpeedMultiplier = 1; + private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(); public CharStat(L2Character activeChar) @@ -109,9 +114,14 @@ public class CharStat /** * @return the Attack Speed multiplier (base+modifier) of the L2Character to get proper animations. */ - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { - return (float) (((1.1) * getPAtkSpd()) / _activeChar.getTemplate().getBasePAtkSpd()); + return _attackSpeedMultiplier; + } + + public final double getMAttackSpeedMultiplier() + { + return _mAttackSpeedMultiplier; } /** @@ -801,6 +811,9 @@ public class CharStat // Merge with additional stats _additionalAdd.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeAdd(holder.getStat(), holder.getValue())); _additionalMul.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeMul(holder.getStat(), holder.getValue())); + + _attackSpeedMultiplier = Formulas.calcAtkSpdMultiplier(_activeChar); + _mAttackSpeedMultiplier = Formulas.calcMAtkSpdMultiplier(_activeChar); } finally { diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java index 7083d575af..d98d83bb9b 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java @@ -100,6 +100,8 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable private int _spiritShotChance; private int _minSkillChance; private int _maxSkillChance; + private double _hitTimeFactor; + private double _hitTimeFactorSkill; private Map _skills; private Map> _aiSkillLists; private Set _clans; @@ -184,6 +186,9 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable _minSkillChance = set.getInt("minSkillChance", 7); _maxSkillChance = set.getInt("maxSkillChance", 15); + _hitTimeFactor = set.getInt("hit_time", 100) / 100d; + _hitTimeFactorSkill = set.getInt("hit_time_skill", 100) / 100d; + _collisionRadiusGrown = set.getDouble("collisionRadiusGrown", 0); _collisionHeightGrown = set.getDouble("collisionHeightGrown", 0); @@ -502,6 +507,16 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable return _maxSkillChance; } + public double getHitTimeFactor() + { + return _hitTimeFactor; + } + + public double getHitTimeFactorSkill() + { + return _hitTimeFactorSkill; + } + @Override public Map getSkills() { diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java index 068361251b..58a6ee155c 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java @@ -92,13 +92,14 @@ public class SkillCaster implements Runnable private final Skill _skill; private final L2ItemInstance _item; private final SkillCastingType _castingType; - private final int _castTime; + private int _hitTime; + private int _cancelTime; private int _coolTime; private Collection _targets; private ScheduledFuture _task; private int _phase; - private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed, int castTime) + private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed) { Objects.requireNonNull(caster); Objects.requireNonNull(skill); @@ -109,7 +110,8 @@ public class SkillCaster implements Runnable _skill = skill; _item = item; _castingType = castingType; - _castTime = castTime; + + calcSkillTiming(caster, skill); } /** @@ -171,10 +173,8 @@ public class SkillCaster implements Runnable return null; } - castTime = castTime > -1 ? castTime : Formulas.calcHitTime(caster, skill); - // Schedule a thread that will execute 500ms before casting time is over (for animation issues and retail handling). - final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed, castTime); + final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed); skillCaster.run(); return skillCaster; } @@ -198,13 +198,13 @@ public class SkillCaster implements Runnable case 0: // Start skill casting. { hasNextPhase = startCasting(); - nextTaskDelay = _castTime; + nextTaskDelay = _hitTime; break; } case 1: // Launch the skill. { hasNextPhase = launchSkill(); - nextTaskDelay = Formulas.SKILL_LAUNCH_TIME; + nextTaskDelay = _cancelTime; break; } case 2: // Finish launching and apply effects. @@ -238,7 +238,7 @@ public class SkillCaster implements Runnable } _coolTime = Formulas.calcAtkSpd(caster, _skill, _skill.getCoolTime()); // TODO Get proper formula of this. - final int displayedCastTime = _castTime + Formulas.SKILL_LAUNCH_TIME; // For client purposes, it must be displayed to player the skill casting time + launch time. + final int displayedCastTime = _hitTime + _cancelTime; // For client purposes, it must be displayed to player the skill casting time + launch time. final boolean instantCast = (_castingType == SkillCastingType.SIMULTANEOUS) || _skill.isAbnormalInstant() || _skill.isWithoutAction(); // Add this SkillCaster to the creature so it can be marked as casting. @@ -762,6 +762,23 @@ public class SkillCaster implements Runnable } } + private void calcSkillTiming(L2Character creature, Skill skill) + { + final double timeFactor = Formulas.calcSkillTimeFactor(creature, skill); + final double cancelTime = Formulas.calcSkillCancelTime(creature, skill); + if (skill.getOperateType().isChanneling()) + { + _hitTime = (int) Math.max(skill.getHitTime() - cancelTime, 0); + _cancelTime = 2866; + } + else + { + _hitTime = (int) Math.max((skill.getHitTime() / timeFactor) - cancelTime, 0); + _cancelTime = (int) cancelTime; + } + _coolTime = (int) (skill.getCoolTime() / timeFactor); // cooltimeMillis / timeFactor + } + public static void triggerCast(L2Character activeChar, L2Character target, Skill skill) { triggerCast(activeChar, target, skill, null, true); diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java index 9d1e060bce..cfcb2aaf20 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -30,6 +30,7 @@ import com.l2jmobius.gameserver.enums.DispelSlotType; import com.l2jmobius.gameserver.enums.Position; import com.l2jmobius.gameserver.enums.ShotType; import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance; import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance; @@ -427,80 +428,57 @@ public final class Formulas return (int) ((skillTime / attacker.getPAtkSpd()) * 300); } - /** - * TODO: Implement those: - *
    - *
  • Skill cool time is block player from doing anything (moving, casting, attacking).
  • - *
  • Seems hardcoded channeling value is not used for the skill task
  • - *
- * @param creature - * @param skill - * @return the hit time of the skill. - */ - public static int calcHitTime(L2Character creature, Skill skill) + public static double calcAtkSpdMultiplier(L2Character creature) { - int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME; - - // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd). - if (!skill.isStatic()) - { - skillTime = calcAtkSpd(creature, skill, skillTime); - } - // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS) - if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS))) - { - skillTime = (int) (0.6 * skillTime); - } - - return Math.max(skillTime, 0); + double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! + double dexBonus = BaseStats.DEX.calcBonus(creature); + double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 + double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); + double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); + return (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); + } + + public static double calcMAtkSpdMultiplier(L2Character creature) + { + final double armorBonus = 1; // TODO: Implement me! + final double witBonus = BaseStats.WIT.calcBonus(creature); + final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); + final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); + return ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); } /** - * TODO: Implement armor bonus and NPC Divider * @param creature * @param skill - * @return + * @return factor divisor for skill hit time and cancel time. */ public static double calcSkillTimeFactor(L2Character creature, Skill skill) { - double factor = 0; - if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3 + if (skill.getOperateType().isChanneling() || (skill.getMagicType() == 2) || (skill.getMagicType() == 4) || (skill.getMagicType() == 21)) { - final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! - final double dexBonus = BaseStats.DEX.calcBonus(creature); - final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 - final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); - final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); - factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); - } - else if (skill.isMagic()) // is_magic = 1 - { - final double armorBonus = 1; // TODO: Implement me! - final double witBonus = BaseStats.WIT.calcBonus(creature); - final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed - final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); - factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); - } - else if (skill.isStatic()) // is_magic = 2 - { - factor = 1; + return 1.0d; } - if (skill.isChanneling()) // operate type = 5 or 6 or 7 + double factor = 0.0; + if (skill.getMagicType() == 1) { - factor = 1; + final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values + factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time) + } + else + { + factor = creature.getAttackSpeedMultiplier(); } - if (creature.isNpc() || creature.isSummon()) + if (creature.isNpc()) { - // TODO: Implement me! - // if (attacker.unk08B0 > 0) + double npcFactor = ((L2Npc) creature).getTemplate().getHitTimeFactorSkill(); + if (npcFactor > 0) { - // factor /= attacker.unk08B0; + factor /= npcFactor; } } - - return Math.max(factor, 0.01); + return Math.max(0.01, factor); } public static double calcSkillCancelTime(L2Character creature, Skill skill) diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java index ba5ba7e725..879c53a403 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java @@ -62,23 +62,23 @@ public interface IStatsFunction default double calcWeaponBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElseGet(() -> + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElseGet(() -> { if (creature.isPet()) { final L2PetInstance pet = (L2PetInstance) creature; final L2ItemInstance weapon = pet.getActiveWeaponInstance(); - final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateBalue; + final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateValue; return baseVal + (weapon != null ? weapon.getItem().getStats(stat, baseVal) : 0); } else if (creature.isPlayer()) { final L2ItemInstance weapon = creature.getActiveWeaponInstance(); - return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateBalue) : baseTemplateBalue); + return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateValue) : baseTemplateValue); } - return baseTemplateBalue; + return baseTemplateValue; }); return baseValue; @@ -86,8 +86,8 @@ public interface IStatsFunction default double calcWeaponPlusBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElse(baseTemplateBalue); + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElse(baseTemplateValue); if (creature.isPlayable()) { diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java index d0e7aac157..febb6e8367 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java @@ -87,7 +87,7 @@ public class CharInfo implements IClientOutgoingPacket _heading = _activeChar.getHeading(); _mAtkSpd = _activeChar.getMAtkSpd(); _pAtkSpd = _activeChar.getPAtkSpd(); - _attackSpeedMultiplier = _activeChar.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) _activeChar.getAttackSpeedMultiplier(); _moveMultiplier = cha.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(cha.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(cha.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java index 8e53e21a1d..e9ec1cfcd3 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java @@ -281,7 +281,7 @@ public class ExPetInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java index 4dde470846..37c2af813a 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java @@ -58,7 +58,7 @@ public class FakePlayerInfo implements IClientOutgoingPacket _heading = npc.getHeading(); _mAtkSpd = npc.getMAtkSpd(); _pAtkSpd = npc.getPAtkSpd(); - _attackSpeedMultiplier = npc.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) npc.getAttackSpeedMultiplier(); _moveMultiplier = npc.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(npc.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(npc.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java index c294edab2a..60cd3a09a5 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java @@ -314,7 +314,7 @@ public class NpcInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _npc.getStat().getMovementSpeedMultiplier()); - packet.writeE(_npc.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _npc.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java index 9fbb4c1289..e62cba55ba 100644 --- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java +++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java @@ -282,7 +282,7 @@ public class SummonInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9d48799cd6..deb1701ba3 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -4230,7 +4230,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe return getStat().getMagicEvasionRate(); } - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { return getStat().getAttackSpeedMultiplier(); } diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java index 756ee0146c..1a76172f79 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/stat/CharStat.java @@ -44,6 +44,7 @@ import com.l2jmobius.gameserver.model.skills.AbnormalType; import com.l2jmobius.gameserver.model.skills.BuffInfo; import com.l2jmobius.gameserver.model.skills.Skill; import com.l2jmobius.gameserver.model.skills.SkillConditionScope; +import com.l2jmobius.gameserver.model.stats.Formulas; import com.l2jmobius.gameserver.model.stats.MoveType; import com.l2jmobius.gameserver.model.stats.Stats; import com.l2jmobius.gameserver.model.stats.StatsHolder; @@ -76,6 +77,10 @@ public class CharStat private final Deque _additionalMul = new ConcurrentLinkedDeque<>(); private final Map _fixedValue = new ConcurrentHashMap<>(); + /** Values to be recalculated after every stat update */ + private double _attackSpeedMultiplier = 1; + private double _mAttackSpeedMultiplier = 1; + private final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(); public CharStat(L2Character activeChar) @@ -109,9 +114,14 @@ public class CharStat /** * @return the Attack Speed multiplier (base+modifier) of the L2Character to get proper animations. */ - public final float getAttackSpeedMultiplier() + public final double getAttackSpeedMultiplier() { - return (float) (((1.1) * getPAtkSpd()) / _activeChar.getTemplate().getBasePAtkSpd()); + return _attackSpeedMultiplier; + } + + public final double getMAttackSpeedMultiplier() + { + return _mAttackSpeedMultiplier; } /** @@ -801,6 +811,9 @@ public class CharStat // Merge with additional stats _additionalAdd.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeAdd(holder.getStat(), holder.getValue())); _additionalMul.stream().filter(holder -> holder.verifyCondition(_activeChar)).forEach(holder -> mergeMul(holder.getStat(), holder.getValue())); + + _attackSpeedMultiplier = Formulas.calcAtkSpdMultiplier(_activeChar); + _mAttackSpeedMultiplier = Formulas.calcMAtkSpdMultiplier(_activeChar); } finally { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java index 457c76f46b..b87e1d3dbb 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/templates/L2NpcTemplate.java @@ -100,6 +100,8 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable private int _spiritShotChance; private int _minSkillChance; private int _maxSkillChance; + private double _hitTimeFactor; + private double _hitTimeFactorSkill; private Map _skills; private Map> _aiSkillLists; private Set _clans; @@ -184,6 +186,9 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable _minSkillChance = set.getInt("minSkillChance", 7); _maxSkillChance = set.getInt("maxSkillChance", 15); + _hitTimeFactor = set.getInt("hit_time", 100) / 100d; + _hitTimeFactorSkill = set.getInt("hit_time_skill", 100) / 100d; + _collisionRadiusGrown = set.getDouble("collisionRadiusGrown", 0); _collisionHeightGrown = set.getDouble("collisionHeightGrown", 0); @@ -502,6 +507,16 @@ public final class L2NpcTemplate extends L2CharTemplate implements IIdentifiable return _maxSkillChance; } + public double getHitTimeFactor() + { + return _hitTimeFactor; + } + + public double getHitTimeFactorSkill() + { + return _hitTimeFactorSkill; + } + @Override public Map getSkills() { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java index 068361251b..58a6ee155c 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/skills/SkillCaster.java @@ -92,13 +92,14 @@ public class SkillCaster implements Runnable private final Skill _skill; private final L2ItemInstance _item; private final SkillCastingType _castingType; - private final int _castTime; + private int _hitTime; + private int _cancelTime; private int _coolTime; private Collection _targets; private ScheduledFuture _task; private int _phase; - private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed, int castTime) + private SkillCaster(L2Character caster, L2Object target, Skill skill, L2ItemInstance item, SkillCastingType castingType, boolean ctrlPressed, boolean shiftPressed) { Objects.requireNonNull(caster); Objects.requireNonNull(skill); @@ -109,7 +110,8 @@ public class SkillCaster implements Runnable _skill = skill; _item = item; _castingType = castingType; - _castTime = castTime; + + calcSkillTiming(caster, skill); } /** @@ -171,10 +173,8 @@ public class SkillCaster implements Runnable return null; } - castTime = castTime > -1 ? castTime : Formulas.calcHitTime(caster, skill); - // Schedule a thread that will execute 500ms before casting time is over (for animation issues and retail handling). - final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed, castTime); + final SkillCaster skillCaster = new SkillCaster(caster, target, skill, item, castingType, ctrlPressed, shiftPressed); skillCaster.run(); return skillCaster; } @@ -198,13 +198,13 @@ public class SkillCaster implements Runnable case 0: // Start skill casting. { hasNextPhase = startCasting(); - nextTaskDelay = _castTime; + nextTaskDelay = _hitTime; break; } case 1: // Launch the skill. { hasNextPhase = launchSkill(); - nextTaskDelay = Formulas.SKILL_LAUNCH_TIME; + nextTaskDelay = _cancelTime; break; } case 2: // Finish launching and apply effects. @@ -238,7 +238,7 @@ public class SkillCaster implements Runnable } _coolTime = Formulas.calcAtkSpd(caster, _skill, _skill.getCoolTime()); // TODO Get proper formula of this. - final int displayedCastTime = _castTime + Formulas.SKILL_LAUNCH_TIME; // For client purposes, it must be displayed to player the skill casting time + launch time. + final int displayedCastTime = _hitTime + _cancelTime; // For client purposes, it must be displayed to player the skill casting time + launch time. final boolean instantCast = (_castingType == SkillCastingType.SIMULTANEOUS) || _skill.isAbnormalInstant() || _skill.isWithoutAction(); // Add this SkillCaster to the creature so it can be marked as casting. @@ -762,6 +762,23 @@ public class SkillCaster implements Runnable } } + private void calcSkillTiming(L2Character creature, Skill skill) + { + final double timeFactor = Formulas.calcSkillTimeFactor(creature, skill); + final double cancelTime = Formulas.calcSkillCancelTime(creature, skill); + if (skill.getOperateType().isChanneling()) + { + _hitTime = (int) Math.max(skill.getHitTime() - cancelTime, 0); + _cancelTime = 2866; + } + else + { + _hitTime = (int) Math.max((skill.getHitTime() / timeFactor) - cancelTime, 0); + _cancelTime = (int) cancelTime; + } + _coolTime = (int) (skill.getCoolTime() / timeFactor); // cooltimeMillis / timeFactor + } + public static void triggerCast(L2Character activeChar, L2Character target, Skill skill) { triggerCast(activeChar, target, skill, null, true); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java index d807bcdfd7..b6cc5a08c7 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -30,6 +30,7 @@ import com.l2jmobius.gameserver.enums.DispelSlotType; import com.l2jmobius.gameserver.enums.Position; import com.l2jmobius.gameserver.enums.ShotType; import com.l2jmobius.gameserver.model.actor.L2Character; +import com.l2jmobius.gameserver.model.actor.L2Npc; import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance; import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance; import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance; @@ -427,80 +428,57 @@ public final class Formulas return (int) ((skillTime / attacker.getPAtkSpd()) * 300); } - /** - * TODO: Implement those: - *
    - *
  • Skill cool time is block player from doing anything (moving, casting, attacking).
  • - *
  • Seems hardcoded channeling value is not used for the skill task
  • - *
- * @param creature - * @param skill - * @return the hit time of the skill. - */ - public static int calcHitTime(L2Character creature, Skill skill) + public static double calcAtkSpdMultiplier(L2Character creature) { - int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME; - - // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd). - if (!skill.isStatic()) - { - skillTime = calcAtkSpd(creature, skill, skillTime); - } - // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS) - if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS))) - { - skillTime = (int) (0.6 * skillTime); - } - - return Math.max(skillTime, 0); + double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! + double dexBonus = BaseStats.DEX.calcBonus(creature); + double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 + double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); + double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); + return (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); + } + + public static double calcMAtkSpdMultiplier(L2Character creature) + { + final double armorBonus = 1; // TODO: Implement me! + final double witBonus = BaseStats.WIT.calcBonus(creature); + final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); + final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); + return ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); } /** - * TODO: Implement armor bonus and NPC Divider * @param creature * @param skill - * @return + * @return factor divisor for skill hit time and cancel time. */ public static double calcSkillTimeFactor(L2Character creature, Skill skill) { - double factor = 0; - if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3 + if (skill.getOperateType().isChanneling() || (skill.getMagicType() == 2) || (skill.getMagicType() == 4) || (skill.getMagicType() == 21)) { - final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me! - final double dexBonus = BaseStats.DEX.calcBonus(creature); - final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868 - final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED); - final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED); - factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333); - } - else if (skill.isMagic()) // is_magic = 1 - { - final double armorBonus = 1; // TODO: Implement me! - final double witBonus = BaseStats.WIT.calcBonus(creature); - final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed - final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED); - factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333); - } - else if (skill.isStatic()) // is_magic = 2 - { - factor = 1; + return 1.0d; } - if (skill.isChanneling()) // operate type = 5 or 6 or 7 + double factor = 0.0; + if (skill.getMagicType() == 1) { - factor = 1; + final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values + factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time) + } + else + { + factor = creature.getAttackSpeedMultiplier(); } - if (creature.isNpc() || creature.isSummon()) + if (creature.isNpc()) { - // TODO: Implement me! - // if (attacker.unk08B0 > 0) + double npcFactor = ((L2Npc) creature).getTemplate().getHitTimeFactorSkill(); + if (npcFactor > 0) { - // factor /= attacker.unk08B0; + factor /= npcFactor; } } - - return Math.max(factor, 0.01); + return Math.max(0.01, factor); } public static double calcSkillCancelTime(L2Character creature, Skill skill) diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java index ba5ba7e725..879c53a403 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/IStatsFunction.java @@ -62,23 +62,23 @@ public interface IStatsFunction default double calcWeaponBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElseGet(() -> + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + final double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElseGet(() -> { if (creature.isPet()) { final L2PetInstance pet = (L2PetInstance) creature; final L2ItemInstance weapon = pet.getActiveWeaponInstance(); - final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateBalue; + final double baseVal = stat == Stats.PHYSICAL_ATTACK ? pet.getPetLevelData().getPetPAtk() : stat == Stats.MAGIC_ATTACK ? pet.getPetLevelData().getPetMAtk() : baseTemplateValue; return baseVal + (weapon != null ? weapon.getItem().getStats(stat, baseVal) : 0); } else if (creature.isPlayer()) { final L2ItemInstance weapon = creature.getActiveWeaponInstance(); - return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateBalue) : baseTemplateBalue); + return (weapon != null ? weapon.getItem().getStats(stat, baseTemplateValue) : baseTemplateValue); } - return baseTemplateBalue; + return baseTemplateValue; }); return baseValue; @@ -86,8 +86,8 @@ public interface IStatsFunction default double calcWeaponPlusBaseValue(L2Character creature, Stats stat) { - final double baseTemplateBalue = creature.getTemplate().getBaseValue(stat, 0); - double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateBalue)).orElse(baseTemplateBalue); + final double baseTemplateValue = creature.getTemplate().getBaseValue(stat, 0); + double baseValue = creature.getTransformation().filter(transform -> !transform.isStance()).map(transform -> transform.getStats(creature, stat, baseTemplateValue)).orElse(baseTemplateValue); if (creature.isPlayable()) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java index 19b51c80ed..777efe2b80 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/CharInfo.java @@ -87,7 +87,7 @@ public class CharInfo implements IClientOutgoingPacket _heading = _activeChar.getHeading(); _mAtkSpd = _activeChar.getMAtkSpd(); _pAtkSpd = _activeChar.getPAtkSpd(); - _attackSpeedMultiplier = _activeChar.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) _activeChar.getAttackSpeedMultiplier(); _moveMultiplier = cha.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(cha.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(cha.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java index 8e53e21a1d..e9ec1cfcd3 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/ExPetInfo.java @@ -281,7 +281,7 @@ public class ExPetInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java index 2f040f5e21..64ac2bc55c 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/FakePlayerInfo.java @@ -58,7 +58,7 @@ public class FakePlayerInfo implements IClientOutgoingPacket _heading = npc.getHeading(); _mAtkSpd = npc.getMAtkSpd(); _pAtkSpd = npc.getPAtkSpd(); - _attackSpeedMultiplier = npc.getAttackSpeedMultiplier(); + _attackSpeedMultiplier = (float) npc.getAttackSpeedMultiplier(); _moveMultiplier = npc.getMovementSpeedMultiplier(); _runSpd = (int) Math.round(npc.getRunSpeed() / _moveMultiplier); _walkSpd = (int) Math.round(npc.getWalkSpeed() / _moveMultiplier); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java index c294edab2a..60cd3a09a5 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/NpcInfo.java @@ -314,7 +314,7 @@ public class NpcInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _npc.getStat().getMovementSpeedMultiplier()); - packet.writeE(_npc.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _npc.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) { diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java index 99d3a634f8..a3d9499906 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/network/serverpackets/SummonInfo.java @@ -282,7 +282,7 @@ public class SummonInfo extends AbstractMaskPacket if (containsMask(NpcInfoType.SPEED_MULTIPLIER)) { packet.writeE((float) _summon.getStat().getMovementSpeedMultiplier()); - packet.writeE(_summon.getStat().getAttackSpeedMultiplier()); + packet.writeE((float) _summon.getStat().getAttackSpeedMultiplier()); } if (containsMask(NpcInfoType.EQUIPPED)) {