package ai.instance.custom.eternalChallenge;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aionemu.gameserver.ai.AIName;
import com.aionemu.gameserver.custom.instance.CustomInstanceService;
import com.aionemu.gameserver.custom.instance.RoahCustomInstanceHandler;
import com.aionemu.gameserver.custom.instance.neuralnetwork.PlayerModel;
import com.aionemu.gameserver.custom.instance.neuralnetwork.PlayerModelController;
import com.aionemu.gameserver.custom.instance.neuralnetwork.PlayerModelEntry;
import com.aionemu.gameserver.dataholders.loadingutils.adapters.NpcEquipmentList;
import com.aionemu.gameserver.model.ChatType;
import com.aionemu.gameserver.model.PlayerClass;
import com.aionemu.gameserver.model.SkillElement;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.state.CreatureSeeState;
import com.aionemu.gameserver.model.stats.calc.functions.StatSetFunction;
import com.aionemu.gameserver.model.stats.container.PlayerGameStats;
import com.aionemu.gameserver.model.stats.container.StatEnum;
import com.aionemu.gameserver.model.templates.item.ItemTemplate;
import com.aionemu.gameserver.model.templates.item.enums.EquipType;
import com.aionemu.gameserver.model.templates.item.enums.ItemSubType;
import com.aionemu.gameserver.network.aion.serverpackets.SM_MESSAGE;
import com.aionemu.gameserver.skillengine.SkillEngine;
import com.aionemu.gameserver.skillengine.condition.Condition;
import com.aionemu.gameserver.skillengine.condition.DpCondition;
import com.aionemu.gameserver.skillengine.effect.AbnormalState;
import com.aionemu.gameserver.skillengine.model.Effect;
import com.aionemu.gameserver.skillengine.model.Skill;
import com.aionemu.gameserver.skillengine.model.SkillTemplate;
import com.aionemu.gameserver.skillengine.model.SkillType;
import com.aionemu.gameserver.skillengine.properties.Properties.CastState;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.PositionUtil;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.utils.stats.CalculationType;
import com.aionemu.gameserver.world.World;
import com.aionemu.gameserver.world.WorldMapInstance;
import ai.GeneralNpcAI;
/**
* @author Jo
*/
@AIName("custom_instance_boss")
public class CustomInstanceBossAI extends GeneralNpcAI {
private static final Logger log = LoggerFactory.getLogger("CUSTOM_INSTANCE_LOG");
private PlayerModel model;
private Future<?> skillTask, castTimeout;
private int previousSkill, rank;
private List<Integer> skillSet;
private boolean onlyAttack;
public CustomInstanceBossAI(Npc owner) {
super(owner);
}
@Override
public float modifyDamage(Creature attacker, float damage, Effect effect) {
return damage * 0.42f; // pseudo PvP reduce
}
@Override
public float modifyOwnerDamage(float damage, Creature effected, Effect effect) {
return damage * 0.58f; // pseudo PvP reduce
}
@Override
protected void handleSpawned() {
super.handleSpawned();
previousSkill = -1;
WorldMapInstance wmi = getPosition().getWorldMapInstance();
if (!(wmi.getInstanceHandler() instanceof RoahCustomInstanceHandler))
return;
int playerId = wmi.getRegisteredObjects().iterator().next();
rank = CustomInstanceService.getInstance().loadOrCreateRank(playerId).getRank();
Player p = World.getInstance().getPlayer(playerId);
if (p == null) {
log.error("[CI_ROAH] No player object found for player id: " + playerId
+ ". Either the player is offline or the central artifact was destroyed by something else.", new Exception());
return;
}
if (p.getPlayerClass() == PlayerClass.RIDER) {
onlyAttack = true;
} else {
onlyAttack = false;
adaptAppearance(p);
adaptStats(p);
getOwner().setSeeState(CreatureSeeState.SEARCH2);
// model player behavior
skillSet = ((RoahCustomInstanceHandler) wmi.getInstanceHandler()).getSkillSet();
model = ((RoahCustomInstanceHandler) wmi.getInstanceHandler()).getPlayerModel();
}
}
@Override
protected void handleBackHome() {
super.handleBackHome();
// prevent reset-abusing
if (getOwner().getSkillCoolDowns() != null)
getOwner().getSkillCoolDowns().clear();
getLifeStats().setCurrentHpPercent(100);
}
@Override
public void handleCreatureDetected(Creature creature) {
super.handleCreatureDetected(creature);
WorldMapInstance wmi = getPosition().getWorldMapInstance();
if (!(wmi.getInstanceHandler() instanceof RoahCustomInstanceHandler))
return;
if (PositionUtil.getDistance(getPosition().getX(), getPosition().getY(), getPosition().getZ(), creature.getX(), creature.getY(),
creature.getZ()) <= 45 && getPosition().getWorldMapInstance().isRegistered(creature.getObjectId())) {
getAggroList().addHate(creature, 100); // early aggro
if (skillTask == null && !onlyAttack)
skillTask = ThreadPoolManager.getInstance().scheduleAtFixedRate(this::checkSkillRotation, 200, 200);
}
}
private void checkSkillRotation() {
if (!getOwner().isCasting()) {
int skillID = getPlayerSkill();
if (skillID != -1 && castTimeout == null) {
Skill skill = SkillEngine.getInstance().getSkill(getOwner(), skillID, 1, getTarget());
skill.useSkill();
// workaround for slide-cast bug:
getEffectController().setAbnormal(AbnormalState.SANCTUARY);
long castDuration = (long) (skill.getSkillTemplate().getDuration() + (0.3f * getOwner().getGameStats().getAttackSpeed().getCurrent()));
if (skill.getSkillTemplate().getCooldown() == 0) // item skills
castDuration = 0;
castTimeout = ThreadPoolManager.getInstance().schedule(() -> {
getEffectController().unsetAbnormal(AbnormalState.SANCTUARY);
if (getOwner().getCastingSkill() != null)
getOwner().getCastingSkill().cancelCast();
// little break after timeout (for walking)
castTimeout = ThreadPoolManager.getInstance().schedule(() -> castTimeout = null, 1000);
}, castDuration); // after cast / instant skill: 300ms * attack speed
}
}
}
private int getPlayerSkill() {
if (getTarget() == null || !(getTarget() instanceof Creature))
return -1;
// remove shock
if ((getEffectController().isInAnyAbnormalState(AbnormalState.ANY_STUN) || getEffectController().isAbnormalSet(AbnormalState.OPENAERIAL))
&& getOwner().getSkillCoolDown(1968) <= System.currentTimeMillis())
return 283;
if (getEffectController().isInAnyAbnormalState(AbnormalState.CANT_ATTACK_STATE))
return -1;
// compute skill selection:
if (model != null) {
// assess game state:
double[] inputArray = new PlayerModelEntry(getOwner(), -1, (Creature) getTarget()).toStateInputArray(skillSet, previousSkill);
List<Double> output = model.getOutputEstimation(inputArray);
for (int i = 0; i < output.size(); i++) {
Skill skillI = SkillEngine.getInstance().getSkill(getOwner(), skillSet.get(i), 1, getTarget());
if (skillI == null) {
log.warn("Detected a skill input with not existent template [skillId=" + skillSet.get(i) + "].");
output.set(i, -1d);
continue;
}
int cdID = -1; // item skills that have no cdID
boolean isDPskill = false;
if (skillI.getSkillTemplate() != null) {
cdID = skillI.getSkillTemplate().getCooldownId();
if (skillI.getSkillTemplate().getStartconditions() != null) {
for (Condition c : skillI.getSkillTemplate().getStartconditions().getConditions()) {
if (c instanceof DpCondition) {
isDPskill = true;
break;
}
}
}
}
// rule out:
if (isDPskill || !skillI.canUseSkill(CastState.CAST_START)
|| (skillI.getSkillTemplate().getType() == SkillType.MAGICAL && getEffectController().isAbnormalSet(AbnormalState.SILENCE))
|| (skillI.getSkillTemplate().getType() == SkillType.PHYSICAL && getEffectController().isAbnormalSet(AbnormalState.BIND))
|| skillI.getSkillTemplate().isCharge() || skillI.isPointSkill()
|| (cdID != -1 && getOwner().getSkillCoolDown(cdID) > System.currentTimeMillis()))
output.set(i, -1d); // -1 = minimum probability
}
int skillIndex = PlayerModelController.getMaxIndex(output);
if (output.get(skillIndex) == -1) // if no CDs available: auto-attack
return -1;
return skillSet.get(skillIndex);
}
return -1;
}
@Override
public void onEndUseSkill(SkillTemplate skillTemplate, int skillLevel) {
super.onEndUseSkill(skillTemplate, skillLevel);
previousSkill = skillTemplate.getSkillId();
if (castTimeout != null) {
castTimeout.cancel(true);
castTimeout = null;
}
if (skillTemplate.getCooldown() == 0) // item skills: prevent spamming
getOwner().setSkillCoolDown(skillTemplate.getCooldownId(), System.currentTimeMillis() + 60000);
getEffectController().unsetAbnormal(AbnormalState.SANCTUARY);
}
private void adaptAppearance(Player player) {
List<ItemTemplate> equipmentList = new ArrayList<>();
if (player.getEquipment().getMainHandWeapon() != null) // weapons manually to exclude swap weapon slots
equipmentList.add(player.getEquipment().getMainHandWeapon().getItemSkinTemplate());
if (player.getEquipment().getOffHandWeapon() != null)
equipmentList.add(player.getEquipment().getOffHandWeapon().getItemSkinTemplate());
for (Item i : player.getEquipment().getEquippedItems())
if (i.getEquipmentType() == EquipType.ARMOR && i.getItemTemplate().getItemSubType() != ItemSubType.SHIELD)
equipmentList.add(i.getItemSkinTemplate());
NpcEquipmentList v = new NpcEquipmentList();
v.items = equipmentList.toArray(new ItemTemplate[0]);
getOwner().overrideEquipmentList(v);
}
private void adaptStats(Player player) {
PlayerGameStats pgs = player.getGameStats();
List<StatSetFunction> functions = new ArrayList<>();
functions.add(new StatSetFunction(StatEnum.EARTH_RESISTANCE, pgs.getMagicalDefenseFor(SkillElement.EARTH)));
functions.add(new StatSetFunction(StatEnum.FIRE_RESISTANCE, pgs.getMagicalDefenseFor(SkillElement.FIRE)));
functions.add(new StatSetFunction(StatEnum.WATER_RESISTANCE, pgs.getMagicalDefenseFor(SkillElement.WATER)));
functions.add(new StatSetFunction(StatEnum.WIND_RESISTANCE, pgs.getMagicalDefenseFor(SkillElement.WIND)));
functions.add(new StatSetFunction(StatEnum.ABNORMAL_RESISTANCE_ALL, pgs.getAbnormalResistance().getCurrent()));
functions.add(new StatSetFunction(StatEnum.ACCURACY, pgs.getAccuracy().getCurrent()));
functions.add(new StatSetFunction(StatEnum.AGILITY, pgs.getAgility().getCurrent()));
functions.add(new StatSetFunction(StatEnum.ATTACK_RANGE, 2500));
functions.add(new StatSetFunction(StatEnum.BLOCK, pgs.getBlock().getCurrent()));
functions.add(new StatSetFunction(StatEnum.EVASION, pgs.getEvasion().getCurrent()));
functions.add(new StatSetFunction(StatEnum.HEALTH, pgs.getHealth().getCurrent()));
functions.add(new StatSetFunction(StatEnum.KNOWLEDGE, pgs.getKnowledge().getCurrent()));
functions.add(new StatSetFunction(StatEnum.MAGIC_SKILL_BOOST_RESIST, pgs.getMBResist().getCurrent()));
functions.add(new StatSetFunction(StatEnum.MAGICAL_RESIST, pgs.getMResist().getCurrent()));
functions.add(new StatSetFunction(StatEnum.MAGICAL_ACCURACY, pgs.getMAccuracy().getCurrent()));
functions.add(new StatSetFunction(StatEnum.MAGICAL_CRITICAL, pgs.getMCritical().getCurrent()));
functions.add(new StatSetFunction(StatEnum.MAIN_HAND_POWER, pgs.getMainHandPAttack(CalculationType.DISPLAY).getCurrent()));
int maxHP = pgs.getMaxHp().getCurrent();
maxHP += (int) (maxHP * rank / 10f);
if (onlyAttack)
maxHP *= 10;
functions.add(new StatSetFunction(StatEnum.MAXHP, maxHP));
functions.add(new StatSetFunction(StatEnum.MAXMP, pgs.getMaxMp().getCurrent()));
functions.add(new StatSetFunction(StatEnum.OFF_HAND_ACCURACY, pgs.getOffHandPAccuracy().getCurrent()));
functions.add(new StatSetFunction(StatEnum.OFF_HAND_ATTACK_SPEED, pgs.getAttackSpeed().getCurrent()));
functions.add(new StatSetFunction(StatEnum.OFF_HAND_CRITICAL, pgs.getOffHandPCritical().getCurrent()));
functions.add(new StatSetFunction(StatEnum.OFF_HAND_POWER, pgs.getOffHandPAttack(CalculationType.DISPLAY).getCurrent()));
functions.add(new StatSetFunction(StatEnum.PARRY, pgs.getParry().getCurrent()));
functions.add(new StatSetFunction(StatEnum.PHYSICAL_ACCURACY, pgs.getMainHandPAccuracy().getCurrent()));
functions.add(new StatSetFunction(StatEnum.PHYSICAL_DEFENSE, pgs.getPDef().getCurrent()));
functions.add(new StatSetFunction(StatEnum.PHYSICAL_CRITICAL_RESIST, pgs.getPCR().getCurrent()));
functions.add(new StatSetFunction(StatEnum.POWER, pgs.getPower().getCurrent()));
functions.add(new StatSetFunction(StatEnum.SPEED, pgs.getMovementSpeed().getCurrent()));
functions.add(new StatSetFunction(StatEnum.WILL, pgs.getWill().getCurrent()));
functions.add(new StatSetFunction(StatEnum.ATTACK_SPEED, pgs.getAttackSpeed().getCurrent()));
// Work-around for not considered dual wield stats for NPCs
int pAtk = pgs.getMainHandPAttack().getCurrent();
if (player.getEquipment().getOffHandWeapon() != null)
pAtk += pgs.getOffHandPAttack(CalculationType.DISPLAY).getCurrent() / 2;
functions.add(new StatSetFunction(StatEnum.PHYSICAL_ATTACK, pAtk));
if (player.getPlayerClass().isPhysicalClass()) {
functions.add(new StatSetFunction(StatEnum.PHYSICAL_CRITICAL, pgs.getMainHandPCritical().getCurrent()));
} else {
functions.add(new StatSetFunction(StatEnum.MAGICAL_CRITICAL, pgs.getMCritical().getCurrent()));
functions.add(new StatSetFunction(StatEnum.MAGICAL_ATTACK, pgs.getMainHandMAttack().getCurrent()));
functions.add(new StatSetFunction(StatEnum.BOOST_MAGICAL_SKILL, pgs.getMBoost().getCurrent()));
}
getOwner().getGameStats().addEffect(null, functions);
getLifeStats().setCurrentHp(getLifeStats().getMaxHp());
}
private void cancelTasks() {
if (skillTask != null && !skillTask.isCancelled())
skillTask.cancel(true);
if (castTimeout != null && !castTimeout.isCancelled())
castTimeout.cancel(true);
}
@Override
protected void handleDied() {
cancelTasks();
if (model == null)
if (onlyAttack)
PacketSendUtility.broadcastToMap(getOwner(), new SM_MESSAGE(getOwner(), "Remarkable... ", ChatType.BRIGHT_YELLOW_CENTER));
else
PacketSendUtility.broadcastToMap(getOwner(),
new SM_MESSAGE(getOwner(), "Remarkable ... I will ... remember you.", ChatType.BRIGHT_YELLOW_CENTER));
else
PacketSendUtility.broadcastToMap(getOwner(), new SM_MESSAGE(getOwner(), "One day ... I will ... suppress you!", ChatType.BRIGHT_YELLOW_CENTER));
super.handleDied();
}
@Override
protected void handleDespawned() {
cancelTasks();
super.handleDespawned();
}
}