package com.aionemu.gameserver.skillengine.effect;
import java.util.Collections;
import java.util.List;
import javax.xml.bind.annotation.*;
import org.slf4j.LoggerFactory;
import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.ai.poll.AIQuestion;
import com.aionemu.gameserver.controllers.attack.AttackResult;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.model.SkillElement;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.Summon;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.stats.container.StatEnum;
import com.aionemu.gameserver.model.templates.npc.NpcTemplate;
import com.aionemu.gameserver.skillengine.change.Change;
import com.aionemu.gameserver.skillengine.condition.Conditions;
import com.aionemu.gameserver.skillengine.effect.modifier.ActionModifier;
import com.aionemu.gameserver.skillengine.effect.modifier.ActionModifiers;
import com.aionemu.gameserver.skillengine.model.*;
import com.aionemu.gameserver.utils.stats.StatFunctions;
/**
* @author ATracer
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Effect")
public abstract class EffectTemplate {
protected ActionModifiers modifiers;
protected List<Change> change;
@XmlAttribute
protected int effectid;
@XmlAttribute(required = true)
protected int duration2;
@XmlAttribute
protected int duration1;
@XmlAttribute(name = "randomtime")
protected int randomTime;
@XmlAttribute(name = "e")
protected int position;
@XmlAttribute(name = "basiclvl")
protected int basicLvl;
@XmlAttribute(name = "hittype")
protected HitType hitType = HitType.EVERYHIT;
@XmlAttribute(name = "hittypeprob2")
protected int hitTypeProb = 100;
@XmlAttribute(name = "element")
protected SkillElement element = SkillElement.NONE;
@XmlElement(name = "subeffect")
protected SubEffect subEffect;
@XmlElement(name = "conditions")
protected Conditions effectConditions;
@XmlElement(name = "subconditions")
protected Conditions effectSubConditions;
@XmlAttribute(name = "hoptype")
protected HopType hopType;
@XmlAttribute(name = "hopa")
protected int hopA; // effects the aggro-value (hate)
@XmlAttribute(name = "hopb")
protected int hopB; // effects the aggro-value (hate)
@XmlAttribute(name = "noresist")
protected boolean noResist;
@XmlAttribute(name = "accmod1")
protected int accMod1;// accdelta
@XmlAttribute(name = "accmod2")
protected int accMod2;// accvalue
@XmlAttribute(name = "preeffect")
protected int[] preEffects;
@XmlAttribute(name = "preeffect_prob")
protected int preEffectProb = 100;
@XmlAttribute(name = "critprobmod2")
protected int critProbMod2 = 100;
@XmlAttribute(name = "critadddmg1")
protected int critAddDmg1 = 0;
@XmlAttribute(name = "critadddmg2")
protected int critAddDmg2 = 0;
@XmlAttribute
protected int value;
@XmlAttribute
protected int delta;
@XmlAttribute(name = "skill_efficiency")
protected int skillEfficiency;
@XmlAttribute(name = "max_damage_chance")
protected int maxDamageChance;
@XmlAttribute(name = "max_damage_delta")
protected int maxDamageDelta;
/**
* @return the value
*/
public int getValue() {
return value;
}
/**
* @return the delta
*/
public int getDelta() {
return delta;
}
/**
* @return the duration2
*/
public int getDuration2() {
return duration2;
}
/**
* @return the duration1
*/
public int getDuration1() {
return duration1;
}
/**
* @return the randomtime
*/
public int getRandomTime() {
return randomTime;
}
/**
* @return the modifiers
*/
public ActionModifiers getModifiers() {
return modifiers;
}
/**
* @return the change
*/
public List<Change> getChange() {
return change;
}
/**
* @return the effectid
*/
public int getEffectId() {
return effectid;
}
/**
* @return the position
*/
public int getPosition() {
return position;
}
/**
* @return the basicLvl
*/
public int getBasicLvl() {
return basicLvl;
}
/**
* @return the element
*/
public SkillElement getElement() {
return element;
}
/**
* @return the preEffectProb
*/
public int getPreEffectProb() {
return preEffectProb;
}
private int[] getPreEffects() {
return preEffects;
}
/**
* @return the critProbMod2
*/
public int getCritProbMod2() {
return critProbMod2;
}
/**
* @return the critAddDmg1
*/
public int getCritAddDmg1() {
return critAddDmg1;
}
/**
* @return the critAddDmg2
*/
public int getCritAddDmg2() {
return critAddDmg2;
}
/**
* Gets the effect conditions status
*
* @return list of Conditions for effect template
*/
public Conditions getEffectConditions() {
return effectConditions;
}
/**
* Gets the sub effect conditions status
*
* @return list of Conditions for sub effects within effect template
*/
public Conditions getEffectSubConditions() {
return effectSubConditions;
}
public ActionModifier getActionModifiers(Effect effect) {
if (modifiers == null)
return null;
// Only one of modifiers will be applied now
for (ActionModifier modifier : modifiers.getActionModifiers()) {
if (modifier.check(effect))
return modifier;
}
return null;
}
/**
* @return the subEffect
*/
public SubEffect getSubEffect() {
return subEffect;
}
/**
* @return the accMod1
*/
public int getAccMod1() {
return accMod1;
}
/**
* @return the accMod2
*/
public int getAccMod2() {
return accMod2;
}
/**
* @return the base value (damage, heal, etc.) according to the skill template, it should equal the value stated in the skill description
*/
protected int calculateBaseValue(Effect effect) {
return value + delta * effect.getSkillLevel();
}
/**
* Calculate effect result
*
* @param effect
*/
public void calculate(Effect effect) {
calculate(effect, null, null, element);
}
public boolean calculate(Effect effect, StatEnum statEnum, SpellStatus spellStatus) {
return calculate(effect, statEnum, spellStatus, element);
}
/**
* 1) check conditions 2) check preeffect 3) check effectresistrate 4) check noresist 5) decide if its magical or physical effect 6) physical -
* check cannotmiss 7) check magic resist / dodge 8) addsuccess exceptions: buffbind buffsilence buffsleep buffstun randommoveloc recallinstant
* returneffect returnpoint shieldeffect signeteffect summoneffect xpboosteffect
*
* @param effect
* @param statEnum
* @param spellStatus
*/
public boolean calculate(Effect effect, StatEnum statEnum, SpellStatus spellStatus, SkillElement element) {
if (effect.getSkillTemplate().isPassive()) {
this.addSuccessEffect(effect, spellStatus);
return true;
}
if (statEnum != null && isAlteredState(statEnum) && isImmuneToAbnormal(effect, statEnum)) {
return false;
}
if (!effect.isForcedEffect()) {
if (!validateEffectConditions(effect))
return false;
if (!validatePreEffects(effect))
return false;
if (getPosition() == 1 ? !validateFirstEffect(effect, statEnum) : !validateSecondaryEffect(effect, statEnum))
return false;
}
addSuccessEffect(effect, spellStatus);
calculateDamage(effect);
return true;
}
private boolean validateEffectConditions(Effect effect) {
return effectConditions == null || effectConditions.validate(effect);
}
private boolean validatePreEffects(Effect effect) {
if (getPreEffects() != null) {
for (int position : getPreEffects()) {
if (!effect.isInSuccessEffects(position))
return false;
}
if (Rnd.chance() >= getPreEffectProb())
return false;
}
return true;
}
private boolean validateFirstEffect(Effect effect, StatEnum statEnum) {
if (!calculateEffectResistRate(effect, statEnum)) {
return false;
}
if (canDodgeOrResist(effect) && isDodgedOrResisted(effect)) {
return false;
}
return true;
}
private boolean validateSecondaryEffect(Effect effect, StatEnum statEnum) {
if (canDodgeOrResist(effect) && (!calculateEffectResistRate(effect, statEnum) || isDodgedOrResisted(effect))) {
EffectTemplate firstEffect = effect.effectInPos(1);
if (!(firstEffect instanceof DamageEffect)) {
effect.getSuccessEffects().remove(firstEffect);
}
return false;
}
return true;
}
protected boolean canDodgeOrResist(Effect effect) {
if (noResist)
return false;
if (effect.getSkillTemplate().getActivationAttribute() == ActivationAttribute.TOGGLE) // Sprinting must not be dodged by Focused Evasion
return false;
return true;
}
private boolean isDodgedOrResisted(Effect effect) {
int accuracyModifier = accMod2 + accMod1 * effect.getSkillLevel() + effect.getAccModBoost();
if (effect.getSkillTemplate().getSubType() == SkillSubType.DEBUFF)
accuracyModifier += effect.getEffector().getGameStats().getStat(StatEnum.BOOST_RESIST_DEBUFF, 0).getCurrent();
if (element == SkillElement.NONE)
return StatFunctions.checkIsDodgedHit(effect.getEffector(), effect.getEffected(), accuracyModifier);
return Rnd.get(1, 1000) <= StatFunctions.calculateMagicalResistRate(effect.getEffector(), effect.getEffected(), accuracyModifier, element);
}
private void addSuccessEffect(Effect effect, SpellStatus spellStatus) {
effect.addSuccessEffect(this);
if (spellStatus != null)
effect.setSpellStatus(spellStatus);
}
/**
* Apply effect to effected
*
* @param effect
*/
public abstract void applyEffect(Effect effect);
/**
* Start effect on effected
*
* @param effect
*/
public void startEffect(Effect effect) {
}
public void calculateDamage(Effect effect) {
// evaluate skill reflect for non-dmg skills
AttackResult attackResult = new AttackResult(0, effect.getAttackStatus(), hitType);
effect.getEffected().getObserveController().checkShieldStatus(Collections.singletonList(attackResult), effect, effect.getEffector(),
ShieldType.SKILL_REFLECTOR);
if (attackResult.getShieldType() != 0) {
effect.setShieldDefense(effect.getShieldDefense() | attackResult.getShieldType());
effect.setReflectedSkillId(attackResult.getReflectedSkillId());
}
}
/**
* @param effect
*/
public void calculateSubEffect(Effect effect) {
if (subEffect == null)
return;
ActionModifiers mod = this.getModifiers();
if (mod != null) {
ActionModifier modifier = this.getActionModifiers(effect);
if (modifier == null) {
return;
}
}
// Pre-Check for sub effect conditions
if (!effectSubConditionsCheck(effect)) {
effect.setSubEffectAborted(true);
return;
}
// chance to trigger subeffect
if (Rnd.chance() >= subEffect.getChance())
return;
SkillTemplate template = DataManager.SKILL_DATA.getSkillTemplate(subEffect.getSkillId());
int level = 1;
int accBoost = effect.getAccModBoost();
if (subEffect.isAddEffect()) { // Only used by signet bursts
level = effect.getSignetBurstedCount();
accBoost = Short.MAX_VALUE; // sub effects cannot be resisted by magic resist in case of signet bursts
}
Effect newEffect = new Effect(effect.getEffector(), effect.getOriginalEffected(), template, level, null, effect.getForceType(), true);
newEffect.setShieldDefense(effect.getShieldDefense());
newEffect.setAccModBoost(accBoost);
newEffect.initialize();
if (newEffect.getSpellStatus() != SpellStatus.DODGE && newEffect.getSpellStatus() != SpellStatus.RESIST)
effect.setSpellStatus(newEffect.getSpellStatus());
effect.setSubEffect(newEffect);
effect.setSubEffectType(newEffect.getSubEffectType());
effect.setTargetLoc(newEffect.getTargetX(), newEffect.getTargetY(), newEffect.getTargetZ());
}
/**
* Check all sub effect condition statuses for effect
*/
private boolean effectSubConditionsCheck(Effect effect) {
return effectSubConditions == null || effectSubConditions.validate(effect);
}
@SuppressWarnings("fallthrough")
public int calculateHate(Effect effect) {
if (hopType != null) {
int hate = 0;
switch (hopType) {
case DAMAGE:
hate = effect.getReserveds(0).getValue();
case SKILLLV:
int skillLvl = effect.getSkillLevel();
hate += hopB + hopA * skillLvl; // Aggro-value of the effect
break;
default:
throw new UnsupportedOperationException("Unhandled effect type " + hopType + " for hate calculation");
}
return Math.max(1, hate);
}
return 0;
}
public void startSubEffect(Effect effect) {
if (subEffect == null)
return;
// Apply-Check for sub effect conditions
if (effect.isSubEffectAbortedBySubConditions())
return;
if (effect.getSubEffect() != null)
effect.getSubEffect().applyEffect();
}
/**
* Do periodic effect on effected
*
* @param effect
*/
public void onPeriodicAction(Effect effect) {
}
/**
* End effect on effected
*
* @param effect
*/
public void endEffect(Effect effect) {
}
/**
* @return true = no resist, false = resisted
*/
@SuppressWarnings("lossy-conversions")
public boolean calculateEffectResistRate(Effect effect, StatEnum statEnum) {
if (statEnum == null)
return true;
Creature effected = effect.getEffected();
Creature effector = effect.getEffector();
if (effected == null || effected.getGameStats() == null || effector == null || effector.getGameStats() == null)
return false;
// Stun like effects cannot be applied as long as a shield is active
if (isProtectedByShield(effected, statEnum))
return false;
// calculate cumulative resist chance for fear, sleep and paralyze if effector and effected are players
if (effector.getMaster() instanceof Player && effected instanceof Player) {
if (statEnum == StatEnum.FEAR_RESISTANCE && ((Player) effected).getFearCount() >= 3
&& ((Player) effected).validateCumulativeFearResistExpirationTime()) {
if (Rnd.get(1, 1000) <= getCumulativeResistChanceFor(((Player) effected).getFearCount())) {
return false;
}
} else if (statEnum == StatEnum.SLEEP_RESISTANCE && ((Player) effected).getSleepCount() >= 3
&& ((Player) effected).validateCumulativeSleepResistExpirationTime()) {
if (Rnd.get(1, 1000) <= getCumulativeResistChanceFor(((Player) effected).getSleepCount())) {
return false;
}
} else if (statEnum == StatEnum.PARALYZE_RESISTANCE && ((Player) effected).getParalyzeCount() >= 3
&& ((Player) effected).validateCumulativeParalyzeResistExpirationTime()) {
if (Rnd.get(1, 1000) <= getCumulativeResistChanceFor(((Player) effected).getParalyzeCount())) {
return false;
}
}
}
int effectPower = 1000;
if (isAlteredState(statEnum))
effectPower -= effected.getGameStats().getAbnormalResistance().getCurrent();
// effect resistance
effectPower -= effected.getGameStats().getStat(statEnum, 0).getCurrent();
// penetration
StatEnum penetrationStat = this.getPenetrationStat(statEnum);
if (penetrationStat != null)
effectPower += effector.getGameStats().getStat(penetrationStat, 0).getCurrent();
// resist mod
if (effector.isPvpTarget(effected)) { // pvp
int lvlDiff = effected.getLevel() - effector.getLevel();
if (lvlDiff > 4) {
float reductionRate = 0.1f * (lvlDiff - 4); // see https://forums.aiononline.com/topic/25-arena-of-discipline-entries/?page=2#elComment_2213
effectPower *= Math.max(1 - reductionRate, 0.1f);
}
} else if (effected instanceof Npc) { // resist mod PvE
int hpGaugeMod = ((Npc) effected).getObjectTemplate().getRank().ordinal();
effectPower -= hpGaugeMod * 100;
}
return Rnd.get(1, 1000) <= effectPower;
}
private boolean isImmuneToAbnormal(Effect effect, StatEnum statEnum) {
Creature effected = effect.getEffected();
if (effected != effect.getEffector()) {
if (effected instanceof Npc || effected instanceof Summon) {
if (effected.getAi().ask(AIQuestion.IS_IMMUNE_TO_ABNORMAL_STATES))
return true;
if (((NpcTemplate) effected.getObjectTemplate()).getStatsTemplate().getRunSpeed() == 0)
return statEnum == StatEnum.PULLED_RESISTANCE || statEnum == StatEnum.STAGGER_RESISTANCE || statEnum == StatEnum.STUMBLE_RESISTANCE;
}
}
return false;
}
/**
* @return true = it's an altered state effect, false = it is Poison/Bleed dot (normal Dots have statEnum null here)
*/
private boolean isAlteredState(StatEnum stat) {
return switch (stat) {
case BLEED_RESISTANCE, POISON_RESISTANCE -> false;
default -> true;
};
}
private boolean isProtectedByShield(Creature effected, StatEnum stat) {
return switch (stat) {
case PULLED_RESISTANCE, STUMBLE_RESISTANCE, OPENAERIAL_RESISTANCE, SPIN_RESISTANCE, STAGGER_RESISTANCE -> effected.getEffectController().isUnderNormalShield();
default -> false;
};
}
private StatEnum getPenetrationStat(StatEnum statEnum) {
StatEnum toReturn = null;
try {
toReturn = StatEnum.valueOf(statEnum.toString() + "_PENETRATION");
} catch (Exception e) {
LoggerFactory.getLogger(EffectTemplate.class).warn("Missing statenum penetration for " + statEnum.toString());
}
return toReturn;
}
private int getCumulativeResistChanceFor(int resistCount) {
return switch (resistCount) {
case 0, 1, 2 -> 0;
case 3 -> 200;
case 4 -> 400;
default -> 1000;
};
}
}