package com.aionemu.gameserver.controllers.attack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.LoggerFactory;
import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.model.SkillElement;
import com.aionemu.gameserver.model.gameobjects.*;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.items.ItemSlot;
import com.aionemu.gameserver.model.items.NpcEquippedGear;
import com.aionemu.gameserver.model.stats.container.StatEnum;
import com.aionemu.gameserver.model.templates.item.ItemAttackType;
import com.aionemu.gameserver.model.templates.item.enums.ItemGroup;
import com.aionemu.gameserver.model.templates.item.enums.ItemSubType;
import com.aionemu.gameserver.model.templates.npc.NpcTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_TARGET_SELECTED;
import com.aionemu.gameserver.skillengine.change.Func;
import com.aionemu.gameserver.skillengine.effect.*;
import com.aionemu.gameserver.skillengine.effect.modifier.ActionModifier;
import com.aionemu.gameserver.skillengine.model.Effect;
import com.aionemu.gameserver.skillengine.model.EffectReserved;
import com.aionemu.gameserver.skillengine.model.EffectReserved.ResourceType;
import com.aionemu.gameserver.skillengine.model.HitType;
import com.aionemu.gameserver.skillengine.model.SkillType;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.stats.CalculationType;
import com.aionemu.gameserver.utils.stats.StatFunctions;
/**
* @author ATracer
*/
public class AttackUtil {
/**
* This method calculates the physical attack status and damage in the following order: <br>
* 1. calculate status<br>
* 2. calculate main & off hand damage<br>
* 3. apply stat modifiers<br>
* 4. amplify damage by hit count<br>
* @param attacker Creature attacking
* @param attacked Creature being attacked
* @param calculationTypes
* @return {@code List<AttackResult>} containing the results for each hand
*/
public static List<AttackResult> calculatePhysAttackResult(Creature attacker, Creature attacked, CalculationType... calculationTypes) {
AttackStatus attackStatus = calculatePhysicalStatus(attacker, attacked, true, 0, 100, false, false);
List<AttackResult> attackResultList = StatFunctions.calculateAttackDamage(attacker, SkillElement.NONE, attackStatus, calculationTypes);
adjustDamageByStatModifiers(attacker, attacked, attackStatus, attackResultList, SkillElement.NONE);
amplifyDamageByAdditionalHitCount(attacker, attackStatus, attackResultList);
modifyDamageByNpcAi(attacker, attacked, attackResultList);
attacked.getObserveController().checkShieldStatus(attackResultList, null, attacker);
return attackResultList;
}
public static void adjustDamageByStatModifiers(Creature attacker, Creature attacked, AttackStatus status, List<AttackResult> attackResultList, SkillElement element) {
float mainMultiplier = 1;
float offMultiplier = 1;
int reduceMax = Integer.MAX_VALUE;
float reduceRatio = 0;
switch (AttackStatus.getBaseStatus(status)) {
case DODGE:
case RESIST:
return;
case BLOCK:
if (attacked instanceof Player p) {
Item shield = p.getEquipment().getEquippedShield();
if (shield != null) {
reduceMax = shield.getItemTemplate().getWeaponStats().getReduceMax();
reduceRatio = attacked.getGameStats().getReverseStat(StatEnum.DAMAGE_REDUCE, 100).getCurrent() / 100f;
}
} else {
reduceRatio = 10; // NPCs reduce damage by min. 10%. TODO: Implement blocking for npcs without shield + check ratio for different npcs
}
break;
case PARRY:
mainMultiplier *= 0.6f;
offMultiplier *= 0.6f;
break;
}
if (status.isCritical()) {
mainMultiplier = 1.5f;
if (element == SkillElement.NONE) {
ItemGroup mainHandGroup = getWeaponGroup(attacker, true);
if (mainHandGroup != null) {
mainMultiplier = getWeaponMultiplier(mainHandGroup);
ItemGroup offHandGroup = getWeaponGroup(attacker, false);
if (offHandGroup != null) {
offMultiplier = getWeaponMultiplier(offHandGroup);
}
}
}
if (attacked instanceof Player) {
int fortitude;
if (element == SkillElement.NONE) { // if stat != null ? why
fortitude = attacked.getGameStats().getStat(StatEnum.PHYSICAL_CRITICAL_DAMAGE_REDUCE, 0).getCurrent();
} else {
fortitude = attacked.getGameStats().getStat(StatEnum.MAGICAL_CRITICAL_DAMAGE_REDUCE, 0).getCurrent();
}
mainMultiplier = (mainMultiplier - fortitude / 1000f);
offMultiplier = (offMultiplier + fortitude / 1000f);
}
}
int maxListIndex = Math.min(attackResultList.size(), 2);
if (maxListIndex < attackResultList.size()) // should never happen but log just in case
LoggerFactory.getLogger(AttackUtil.class).warn("attackResultList has more elements than expected (" + attackResultList.size() + ")");
for (int i = 0; i < maxListIndex; i++) {
StatEnum defenseStat = element == SkillElement.NONE ? StatEnum.PHYSICAL_DEFENSE : StatEnum.MAGICAL_DEFEND;
float def = attacked.getGameStats().getPDef().getBonus() + StatFunctions.getMovementModifier(attacked, defenseStat,
defenseStat == StatEnum.PHYSICAL_DEFENSE ? attacked.getGameStats().getPDef().getBase() : attacked.getGameStats().getMDef().getBase());
float damage = (StatFunctions.adjustDamageByMovementModifier(attacker,attackResultList.get(i).getDamage()) - (def/10)) * (i == 0 ? mainMultiplier : offMultiplier);
if (reduceRatio > 0) {
float dmgToReduce = damage - (damage * reduceRatio);
if (dmgToReduce > reduceMax) {
dmgToReduce = reduceMax;
}
damage -= dmgToReduce;
}
damage = StatFunctions.adjustDamageByPvpOrPveModifiers(attacker, attacked, damage, 0, false, element);
if (damage < 1) {
damage = 1;
}
attackResultList.get(i).setDamage(damage);
}
}
private static int[] calculateAdditionalHitCount(Creature attacker, AttackStatus status, List<AttackResult> attackList) {
int[] hitCount = new int[2];
if (attacker instanceof Player p && (status != AttackStatus.DODGE && status != AttackStatus.RESIST)) {
Item mainHandWeapon = p.getEquipment().getMainHandWeapon();
if (mainHandWeapon != null) {
hitCount[0] = Rnd.get(0, mainHandWeapon.getItemTemplate().getWeaponStats().getHitCount()) - 1;
if (attackList.size() > 1) {
Item offHandWeapon = p.getEquipment().getOffHandWeapon();
if (offHandWeapon != null && offHandWeapon.getItemTemplate().getItemSubType() != ItemSubType.SHIELD) {
hitCount[1] = Rnd.get(0, offHandWeapon.getItemTemplate().getWeaponStats().getHitCount() - 1);
}
}
}
}
return hitCount;
}
private static void amplifyDamageByAdditionalHitCount(Creature attacker, AttackStatus status, List<AttackResult> attackList) {
int[] hitCount = calculateAdditionalHitCount(attacker, status, attackList);
for (int i = 0; i < hitCount[0] + hitCount[1]; i++) {
if (i < hitCount[0]) { // amplify main hand damage
if (attackList.get(0).getDamage() >= 10)
attackList.add(new AttackResult((int) (attackList.get(0).getDamage() * 0.1), AttackStatus.NORMALHIT, attackList.get(0).getHitType()));
} else { // amplify off hand damage
if (attackList.get(1).getDamage() >= 10)
attackList.add(new AttackResult((int)(attackList.get(1).getDamage() * 0.1), AttackStatus.OFFHAND_NORMALHIT, attackList.get(1).getHitType()));
}
}
}
private static void modifyDamageByNpcAi(Creature attacker, Creature attacked, List<AttackResult> attackStatus) {
if (!(attacker instanceof Npc || attacked instanceof Npc))
return;
for (AttackResult status : attackStatus) {
float modifiedDamage = status.getDamage();
if (attacker instanceof Npc)
modifiedDamage = attacker.getAi().modifyOwnerDamage(modifiedDamage, attacked, null);
if (attacked instanceof Npc)
modifiedDamage = attacked.getAi().modifyDamage(attacker, modifiedDamage, null);
status.setDamage(modifiedDamage);
}
}
private static float calculateBlockedDamage(Creature attacked, float damage) {
int reduceStat = attacked.getGameStats().getReverseStat(StatEnum.DAMAGE_REDUCE, 100).getCurrent();
float reduceVal = damage - (damage * reduceStat / 100);
if (attacked instanceof Player) {
Item shield = ((Player) attacked).getEquipment().getEquippedShield();
if (shield != null) {
int reduceMax = shield.getItemTemplate().getWeaponStats().getReduceMax();
if (reduceMax > 0 && reduceMax < reduceVal)
reduceVal = reduceMax;
}
}
return damage - reduceVal;
}
private static float calculateWeaponCritical(SkillElement element, Creature attacked, float damage, ItemGroup group, int critAddDmg, StatEnum stat, boolean isMain) {
float coeficient = 1.5f;
if (element == SkillElement.NONE && group != null) {
coeficient = getWeaponMultiplier(group);
}
if (stat != null && attacked instanceof Player) { // Strike Fortitude lowers the crit multiplier
switch (stat) {
case PHYSICAL_CRITICAL_DAMAGE_REDUCE, MAGICAL_CRITICAL_DAMAGE_REDUCE -> {
int fortitude = attacked.getGameStats().getStat(stat, 0).getCurrent();
coeficient = isMain ? (coeficient - fortitude / 1000f) : (coeficient + fortitude / 1000f);
}
}
}
// add critical add dmg
coeficient += critAddDmg / 100f;
return damage * coeficient;
}
private static float getWeaponMultiplier(ItemGroup group) {
return switch (group) {
case DAGGER -> 2.3f;
case SWORD -> 2.2f;
case MACE -> 2f;
case GREATSWORD, POLEARM -> 1.8f;
case STAFF, BOW -> 1.7f;
default -> 1.5f;
};
}
public static void calculateSkillResult(Effect effect, int skillDamage, EffectTemplate template, boolean ignoreShield) {
Creature effector = effect.getEffector();
Creature effected = effect.getEffected();
// define values
ActionModifier modifier = template.getActionModifiers(effect);
SkillElement element = template.getElement();
Func func = template instanceof DamageEffect damageEffect ? damageEffect.getMode() : Func.ADD;
int randomDamageType = template instanceof SkillAttackInstantEffect skillAttackInstantEffect ? skillAttackInstantEffect.getRnddmg() : 0;
int critAddDmg = template.getCritAddDmg2() + template.getCritAddDmg1() * effect.getSkillLevel();
boolean useTemplateDmg = isUseTemplateDmg(effect, template);
boolean send = !(template instanceof DelayedSpellAttackInstantEffect) && !(template instanceof ProcAtkInstantEffect);
boolean shouldIncreaseByOneTimeBoost = !(template instanceof ProcAtkInstantEffect);
AttackStatus status = switch (element) {
case NONE -> calculatePhysicalStatus(effector, effected, template, effect.getSkillLevel());
default -> calculateMagicalStatus(effector, effected, template.getCritProbMod2(), true, effect.getSkillTemplate().isMcritApplied());
};
int baseAttack = 0;
float bonus = 0;
HitType ht = HitType.PHHIT;
List<AttackResult> weaponAttack = new ArrayList<>();
float damage = 0;
CalculationType[] calculationTypes = new CalculationType[] { CalculationType.SKILL };
if (effector instanceof Player p && p.getEquipment().hasDualWeaponEquipped(ItemSlot.LEFT_HAND))
calculationTypes = ArrayUtils.add(calculationTypes, CalculationType.DUAL_WIELD);
if (!useTemplateDmg) {
if (effector instanceof SummonedObject && !(effector instanceof Servant)) {
ht = effect.getSkillType() == SkillType.MAGICAL ? HitType.MAHIT : HitType.PHHIT;
baseAttack = effector.getGameStats().getMainHandPAttack(calculationTypes).getBase();
weaponAttack = StatFunctions.calculateAttackDamage(effect.getEffector(), SkillElement.NONE, status, calculationTypes);
} else {
switch (effect.getSkillType()) {
case MAGICAL:
ht = HitType.MAHIT;
baseAttack = effector.getGameStats().getMainHandMAttack(calculationTypes).getBase();
if (baseAttack == 0 && effector.getAttackType() == ItemAttackType.PHYSICAL) { // dirty fix for staffs and maces -.-
calculationTypes = ArrayUtils.add(calculationTypes, CalculationType.APPLY_POWER_SHARD_DAMAGE);
if (element == SkillElement.NONE) { // fix for magical skills which actually inflict physical damage
calculationTypes = ArrayUtils.add(calculationTypes, CalculationType.REMOVE_POWER_SHARD);
weaponAttack = StatFunctions.calculateAttackDamage(effect.getEffector(), SkillElement.NONE, status, calculationTypes);
calculationTypes = ArrayUtils.removeElement(calculationTypes, CalculationType.REMOVE_POWER_SHARD); // remove to prevent power shards being removed again in baseAttack calculation
} else {
calculationTypes = ArrayUtils.add(calculationTypes, CalculationType.REMOVE_POWER_SHARD);
}
baseAttack = effector.getGameStats().getMainHandPAttack(calculationTypes).getBase();
}
break;
default:
if (element == SkillElement.NONE) {
calculationTypes = ArrayUtils.add(calculationTypes, CalculationType.APPLY_POWER_SHARD_DAMAGE);
baseAttack = effector.getGameStats().getMainHandPAttack(calculationTypes).getBase();
calculationTypes = ArrayUtils.add(calculationTypes, CalculationType.REMOVE_POWER_SHARD);
weaponAttack = StatFunctions.calculateAttackDamage(effect.getEffector(), SkillElement.NONE, status, calculationTypes);
} else {
baseAttack = effector.getGameStats().getMainHandMAttack(calculationTypes).getBase();
}
break;
}
}
}
for (AttackResult res : weaponAttack) {
damage += res.getExactDamage();
}
// add skill damage
if (func != null) {
switch (func) {
case ADD -> damage += skillDamage;
case PERCENT -> damage += baseAttack * skillDamage / 100f;
}
}
// add bonus damage
if (modifier != null) {
bonus = modifier.analyze(effect);
switch (modifier.getFunc()) {
case ADD:
break;
case PERCENT:
bonus = baseAttack * bonus / 100f;
break;
}
}
if (!useTemplateDmg) {
float damageMultiplier;
switch (element) {
case NONE -> {
damageMultiplier = effector.getObserveController().getBasePhysicalDamageMultiplier(true);
damage += bonus;
}
default -> {
damageMultiplier = shouldIncreaseByOneTimeBoost ? effector.getObserveController().getBaseMagicalDamageMultiplier() : 1f;
damage = StatFunctions.calculateMagicalSkillDamage(effector, effected, damage, (int) bonus, element, true, true);
}
}
damage = StatFunctions.adjustDamageByMovementModifier(effector, damage);
damage *= damageMultiplier;
}
if (randomDamageType > 0)
damage = randomizeDamage(randomDamageType, damage);
damage = switch (status) {
case CRITICAL_BLOCK, CRITICAL_PARRY, CRITICAL -> calculateWeaponCritical(element, effected, damage, getWeaponGroup(effector, true), critAddDmg, element == SkillElement.NONE ?
StatEnum.PHYSICAL_CRITICAL_DAMAGE_REDUCE : StatEnum.MAGICAL_CRITICAL_DAMAGE_REDUCE, true);
default -> damage;
};
if (element == SkillElement.NONE) {
float def = effected.getGameStats().getPDef().getBonus() + StatFunctions.getMovementModifier(effected, StatEnum.PHYSICAL_DEFENSE,
effected.getGameStats().getPDef().getBase());
damage -= def / 10;
}
switch (AttackStatus.getBaseStatus(status)) {
case BLOCK -> damage = calculateBlockedDamage(effected, damage);
case PARRY -> damage *= 0.6f;
}
if (effector instanceof Npc) {
damage = effector.getAi().modifyOwnerDamage(damage, effected, effect);
}
if (effect.getSkill() != null && effect.getSkill().getEffectedList().size() > 1 && template instanceof DamageEffect damageEffect && damageEffect.isShared()) {
damage /= effect.getSkill().getEffectedList().size();
}
damage = StatFunctions.adjustDamageByPvpOrPveModifiers(effector, effected, damage, effect.getPvpDamage(), useTemplateDmg, element);
if (damage < 0)
damage = 0;
if (effected instanceof Npc) {
damage = effected.getAi().modifyDamage(effector, damage, effect);
}
calculateEffectResult(effect, effected, (int) damage, status, ht, ignoreShield, template.getPosition(), send);
}
private static boolean isUseTemplateDmg(Effect effect, EffectTemplate template) {
if (template instanceof NoReduceSpellATKInstantEffect)
return true;
if (template instanceof ProcAtkInstantEffect && effect.getSkillTemplate().isProvoked() || effect.getStack().startsWith("IDEVENT")) { // proc effects of skills like 8583
// TODO: find pattern or extract <apply_magical_skill_boost_bonus> and <apply_magical_critical> from server files. What about missing ones?
switch (effect.getStack().toLowerCase()) {
case "nwi_delayspell_dd_proca_tal":
case "ngu_vritra_delayspell_dd_proca_tal":
case "sgfi_procts_air":
case "ab1_artifact_hellfire":
case "ldf4b_c3_artifact_tiamat_delayatk":
case "ldf4b_t4_artifact_crystal_dd":
case "ldf4b_t3_artifact_fury_dd":
case "ldf4b_t2_artifact_gravity_openaerial":
case "ldf4b_t2_artifact_gravity_dd":
case "ldf4b_t1_artifact_crack_stumble_mpatk":
case "ldf4b_t1_artifact_crack_dd":
case "idtiamat_tahabata_adddmgtobleed":
case "kn_turnaggressiveeffect":
case "tiamatdown_tiamatagent_bomb":
case "idtiamat_thor_procatk":
case "idyun_vasharti_refdmg_red":
case "idyun_vasharti_refdmg_blue":
case "ldf4b_d3_buff_poison_proc":
case "ldf4b_tatar_procatk":
case "idforest_wave_trico_proclight":
case "idevent01_areadot":
return true;
}
}
return false;
}
private static float randomizeDamage(int randomDamageType, float damage) {
switch (randomDamageType) {
case 1:
switch (Rnd.get(1, 3)) {
case 1 -> damage *= 0.5f;
case 2 -> damage *= 1.5f;
}
break;
case 2:
if (Rnd.chance() < 70)
damage *= 0.6f;
else
damage *= 2;
break;
case 3:
switch (Rnd.get(1, 3)) {
case 1 -> damage *= 1.15f;
case 2 -> damage *= 1.25f;
}
break;
case 4:
damage *= (Rnd.get(25, 100) * 0.02f);
break;
case 6:
if (Rnd.chance() < 30)
damage *= 2;
break;
default:
throw new IllegalArgumentException("Unhandled random damage type rnddmg=\"" + randomDamageType + "\"");
}
return damage;
}
private static void calculateEffectResult(Effect effect, Creature effected, int damage, AttackStatus status, HitType hitType, boolean ignoreShield,
int position, boolean send) {
AttackResult attackResult = new AttackResult(damage, status, hitType);
if (!ignoreShield) {
effected.getObserveController().checkShieldStatus(Collections.singletonList(attackResult), effect, effect.getEffector());
effect.setReflectedDamage(attackResult.getReflectedDamage());
effect.setReflectedSkillId(attackResult.getReflectedSkillId());
effect.setMpAbsorbed(attackResult.getMpAbsorbed());
effect.setMpShieldSkillId(attackResult.getMpShieldSkillId());
effect.setProtectedDamage(attackResult.getProtectedDamage());
effect.setProtectedSkillId(attackResult.getProtectedSkillId());
effect.setProtectorId(attackResult.getProtectorId());
effect.setShieldDefense(attackResult.getShieldType());
}
effect.setReserveds(new EffectReserved(position, attackResult.getDamage(), ResourceType.HP, true, send), false);
effect.setAttackStatus(attackResult.getAttackStatus());
effect.setLaunchSubEffect(attackResult.isLaunchSubEffect());
}
/**
* This method calculates the magical attack status and damage in the following order:<br>
* 1. calculate status<br>
* 2. calculate main & off hand damage<br>
* 3. apply stat modifiers<br>
* 4. amplify damage by hit count<br>
* @param attacker Creature attacking
* @param attacked Creature being attacked
* @param calculationTypes
* @return {@code List<AttackResult>} containing the results for each hand
*/
public static List<AttackResult> calculateMagAttackResult(Creature attacker, Creature attacked, SkillElement element, CalculationType... calculationTypes) {
AttackStatus attackStatus = calculateMagicalStatus(attacker, attacked, 100, false, true);
List<AttackResult> attackResultList = StatFunctions.calculateAttackDamage(attacker, element, attackStatus, calculationTypes);
adjustDamageByStatModifiers(attacker, attacked, attackStatus, attackResultList, element);
amplifyDamageByAdditionalHitCount(attacker, attackStatus, attackResultList);
modifyDamageByNpcAi(attacker, attacked, attackResultList);
attacked.getObserveController().checkShieldStatus(attackResultList, null, attacker);
return attackResultList;
}
public static int calculateMagicalOverTimeSkillResult(Effect effect, float skillDamage, SkillElement element, int position, boolean useMagicBoost,
int criticalProb, int critAddDmg) {
Creature effector = effect.getEffector();
Creature effected = effect.getEffected();
float damage;
if (effector instanceof Trap) {
damage = skillDamage;
} else {
// TODO is damage multiplier used on dot?
float damageMultiplier = effector.getObserveController().getBaseMagicalDamageMultiplier();
damage = StatFunctions.calculateMagicalSkillDamage(effector, effected, skillDamage, 0, element, useMagicBoost, false);
damage = damage * damageMultiplier;
AttackStatus status = effect.getAttackStatus();
// calculate attack status only if it has not been forced already
if (status == AttackStatus.NORMALHIT && position == 1)
status = calculateMagicalStatus(effector, effected, criticalProb, true, effect.getSkillTemplate().isMcritApplied());
switch (status) {
case CRITICAL:
damage = calculateWeaponCritical(element, effected, damage, getWeaponGroup(effector, true), critAddDmg,
StatEnum.MAGICAL_CRITICAL_DAMAGE_REDUCE, true);
break;
}
damage = StatFunctions.adjustDamageByPvpOrPveModifiers(effector, effected, damage, effect.getPvpDamage(), false, element);
}
if (damage < 1)
damage = 1;
if (effected instanceof Npc)
damage = effected.getAi().modifyDamage(effector, damage, effect);
return (int) damage;
}
private static AttackStatus calculatePhysicalStatus(Creature attacker, Creature attacked, EffectTemplate template, int skillLevel) {
int accMod = template.getAccMod2() + template.getAccMod1() * skillLevel;
boolean cannotMiss = template instanceof SkillAttackInstantEffect skillAttackInstantEffect && skillAttackInstantEffect.isCannotmiss();
return calculatePhysicalStatus(attacker, attacked, true, accMod, template.getCritProbMod2(), true, cannotMiss);
}
private static AttackStatus calculatePhysicalStatus(Creature attacker, Creature attacked, boolean isMainHand, int accMod, int criticalProb,
boolean isSkill, boolean cannotMiss) {
AttackStatus status = AttackStatus.NORMALHIT;
if (!cannotMiss) {
if (!isSkill && StatFunctions.checkIsDodgedHit(attacker, attacked, accMod))
status = AttackStatus.DODGE;
else if (attacked instanceof Player player && player.getEquipment().isShieldEquipped()
&& StatFunctions.checkIsBlockedHit(attacker, attacked, accMod))
status = AttackStatus.BLOCK;
else if (attacked instanceof Player && StatFunctions.checkIsParriedHit(attacker, attacked, accMod))
status = AttackStatus.PARRY;
} else {
StatFunctions.checkIsDodgedHit(attacker, attacked, accMod);
StatFunctions.checkIsBlockedHit(attacker, attacked, accMod);
StatFunctions.checkIsParriedHit(attacker, attacked, accMod);
}
if (StatFunctions.checkIsPhysicalCriticalHit(attacker, attacked, isMainHand, criticalProb, isSkill)) {
status = switch (status) {
case BLOCK -> AttackStatus.CRITICAL_BLOCK;
case PARRY -> AttackStatus.CRITICAL_PARRY;
case DODGE -> AttackStatus.CRITICAL_DODGE;
default -> AttackStatus.CRITICAL;
};
}
return isMainHand ? status : AttackStatus.getOffHandStats(status);
}
/**
* Every + 100 delta of (MR - MA) = + 10% to resist<br>
* if the difference is 1000 = 100% resist
*/
public static AttackStatus calculateMagicalStatus(Creature attacker, Creature attacked, int criticalProb, boolean isSkill, boolean applyMcrit) {
if (!isSkill) {
if (Rnd.get(1, 1000) <= StatFunctions.calculateMagicalResistRate(attacker, attacked, 0, SkillElement.NONE))
return AttackStatus.RESIST;
}
if (StatFunctions.calculateMagicalCriticalRate(attacker, attacked, criticalProb, applyMcrit)) {
return AttackStatus.CRITICAL;
}
return AttackStatus.NORMALHIT;
}
public static void cancelCastOn(Creature target) {
target.getKnownList().forEachObject(visibleObject -> {
if (visibleObject instanceof Creature creature && visibleObject.getTarget() == target) {
if (creature.getCastingSkill() != null && creature.getCastingSkill().getFirstTarget().equals(target))
creature.getController().cancelCurrentSkill(null);
}
});
}
/**
* Send a packet to everyone who is targeting creature.
*
* @param object
*/
public static void removeTargetFrom(Creature object) {
removeTargetFrom(object, false);
}
public static void removeTargetFrom(Creature object, boolean validateSee) {
object.getKnownList().forEachPlayer(player -> {
if (player.getTarget() == object) {
if (!validateSee || !player.canSee(object)) {
player.setTarget(null);
PacketSendUtility.sendPacket(player, new SM_TARGET_SELECTED(null));
}
}
});
}
private static ItemGroup getWeaponGroup(Creature effector, boolean mainHand) {
if (effector instanceof Player) {
Item weapon = mainHand ? ((Player) effector).getEquipment().getMainHandWeapon() : ((Player) effector).getEquipment().getOffHandWeapon();
if (weapon != null) {
return weapon.getItemTemplate().getItemGroup();
}
} else if (effector instanceof Npc) {
NpcTemplate temp = DataManager.NPC_DATA.getNpcTemplate(((Npc) effector).getNpcId());
NpcEquippedGear npcGear = temp.getEquipment();
if (npcGear != null && npcGear.getItem(mainHand ? ItemSlot.MAIN_HAND : ItemSlot.MAIN_OFF_HAND) != null) {
return npcGear.getItem(mainHand ? ItemSlot.MAIN_HAND : ItemSlot.MAIN_OFF_HAND).getItemGroup();
}
}
return null;
}
}