Magic casting speed rework.

This commit is contained in:
MobiusDev
2018-01-19 12:44:53 +00:00
parent 2b45dadb70
commit e91682043c
55 changed files with 510 additions and 395 deletions

View File

@@ -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();
}

View File

@@ -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
{

View File

@@ -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()
{

View File

@@ -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);

View File

@@ -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)

View File

@@ -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())
{

View File

@@ -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);

View File

@@ -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))
{

View File

@@ -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);

View File

@@ -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))
{

View File

@@ -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))
{