Magic casting speed rework.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
@@ -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<StatsHolder> _additionalMul = new ConcurrentLinkedDeque<>();
|
||||
private final Map<Stats, Double> _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
|
||||
{
|
||||
|
@@ -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<Integer, Skill> _skills;
|
||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||
private Set<Integer> _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<Integer, Skill> getSkills()
|
||||
{
|
||||
|
@@ -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<L2Object> _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);
|
||||
|
@@ -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:
|
||||
* <ul>
|
||||
* <li>Skill cool time is block player from doing anything (moving, casting, attacking).</li>
|
||||
* <li>Seems hardcoded channeling value is not used for the skill task</li>
|
||||
* </ul>
|
||||
* @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)
|
||||
|
@@ -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())
|
||||
{
|
||||
|
@@ -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);
|
||||
|
@@ -281,7 +281,7 @@ public class ExPetInfo extends AbstractMaskPacket<NpcInfoType>
|
||||
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))
|
||||
{
|
||||
|
@@ -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);
|
||||
|
@@ -314,7 +314,7 @@ public class NpcInfo extends AbstractMaskPacket<NpcInfoType>
|
||||
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))
|
||||
{
|
||||
|
@@ -282,7 +282,7 @@ public class SummonInfo extends AbstractMaskPacket<NpcInfoType>
|
||||
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))
|
||||
{
|
||||
|
Reference in New Issue
Block a user