/* * This file is part of aion-emu <aion-emu.com>. * * aion-emu is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * aion-emu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with aion-emu. If not, see <http://www.gnu.org/licenses/>. */ package com.aionemu.gameserver.controllers; import java.util.List; import java.util.concurrent.Future; import org.apache.log4j.Logger; import com.aionemu.gameserver.ai.AI; import com.aionemu.gameserver.ai.events.Event; import com.aionemu.gameserver.ai.npcai.DummyAi; import com.aionemu.gameserver.ai.state.AIState; import com.aionemu.gameserver.controllers.attack.AttackResult; import com.aionemu.gameserver.controllers.attack.AttackUtil; import com.aionemu.gameserver.model.ChatType; import com.aionemu.gameserver.model.TaskId; import com.aionemu.gameserver.model.gameobjects.Creature; import com.aionemu.gameserver.model.gameobjects.Npc; import com.aionemu.gameserver.model.gameobjects.VisibleObject; import com.aionemu.gameserver.model.gameobjects.player.Player; import com.aionemu.gameserver.model.gameobjects.player.RequestResponseHandler; import com.aionemu.gameserver.model.gameobjects.state.CreatureState; import com.aionemu.gameserver.model.gameobjects.stats.NpcGameStats; import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK; import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS; import com.aionemu.gameserver.network.aion.serverpackets.SM_DIALOG_WINDOW; import com.aionemu.gameserver.network.aion.serverpackets.SM_EMOTION; import com.aionemu.gameserver.network.aion.serverpackets.SM_LOOKATOBJECT; import com.aionemu.gameserver.network.aion.serverpackets.SM_MESSAGE; import com.aionemu.gameserver.network.aion.serverpackets.SM_QUESTION_WINDOW; import com.aionemu.gameserver.network.aion.serverpackets.SM_SELL_ITEM; import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE; import com.aionemu.gameserver.network.aion.serverpackets.SM_TRADELIST; import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.TYPE; import com.aionemu.gameserver.questEngine.model.QuestEnv; import com.aionemu.gameserver.restrictions.RestrictionsManager; import com.aionemu.gameserver.utils.MathUtil; import com.aionemu.gameserver.utils.PacketSendUtility; /** * This class is for controlling Npc's * * @author -Nemesiss-, ATracer (2009-09-29) */ public class NpcController extends CreatureController<Npc> { private static final Logger log = Logger.getLogger(NpcController.class); @Override public void notSee(VisibleObject object, boolean isOutOfRange) { super.notSee(object, isOutOfRange); if(object instanceof Creature) getOwner().getAggroList().remove((Creature) object); if(object instanceof Player) getOwner().getAi().handleEvent(Event.NOT_SEE_PLAYER); } @Override public void see(VisibleObject object) { super.see(object); Npc owner = getOwner(); owner.getAi().handleEvent(Event.SEE_CREATURE); if(object instanceof Player) { owner.getAi().handleEvent(Event.SEE_PLAYER); //TODO check on retail how walking npc is presented, probably need replace emotion // with some state etc. if(owner.getMoveController().isWalking()) PacketSendUtility.sendPacket((Player) object, new SM_EMOTION(owner, 21)); } } @Override public void onRespawn() { cancelTask(TaskId.DECAY); Npc owner = getOwner(); owner.unsetState(CreatureState.DEAD); owner.setState(CreatureState.NPC_IDLE); owner.getLifeStats().setCurrentHpPercent(100); owner.getAggroList().clear(); owner.getAi().handleEvent(Event.RESPAWNED); } public void onDespawn(boolean forced) { if(forced) cancelTask(TaskId.DECAY); Npc owner = getOwner(); if(owner == null || !owner.isSpawned()) return; owner.getAi().handleEvent(Event.DESPAWN); sp.getWorld().despawn(owner); } @Override public void onDie(Creature lastAttacker) { super.onDie(lastAttacker); Npc owner = getOwner(); addTask(TaskId.DECAY, sp.getRespawnService().scheduleDecayTask(this.getOwner())); scheduleRespawn(); PacketSendUtility.broadcastPacket(owner, new SM_EMOTION(owner, 13, 0, lastAttacker == null ? 0 : lastAttacker.getObjectId())); if(lastAttacker == null) lastAttacker = owner.getAggroList().getMostHated();// TODO based on damage; this.doReward(lastAttacker); Creature master = lastAttacker.getMaster(); if(master instanceof Player) { this.doDrop((Player) master); } owner.getAi().handleEvent(Event.DIED); // deselect target at the end owner.setTarget(null); PacketSendUtility.broadcastPacket(owner, new SM_LOOKATOBJECT(owner)); } @Override public Npc getOwner() { return (Npc) super.getOwner(); } @Override public void onDialogRequest(Player player) { getOwner().getAi().handleEvent(Event.TALK); if(sp.getQuestEngine().onDialog(new QuestEnv(getOwner(), player, 0, -1))) return; // TODO need check here PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(getOwner().getObjectId(), 10)); } /** * This method should be called to make forced despawn of NPC and delete it from the world */ public void onDelete() { if(getOwner().isInWorld()) { this.onDespawn(true); this.delete(); } } /** * Handle dialog */ public void onDialogSelect(int dialogId, final Player player, int questId) { Npc npc = getOwner(); int targetObjectId = npc.getObjectId(); if(sp.getQuestEngine().onDialog(new QuestEnv(npc, player, questId, dialogId))) return; switch(dialogId) { case 2: PacketSendUtility.sendPacket(player, new SM_TRADELIST(npc, sp.getTradeService().getTradeListData() .getTradeListTemplate(npc.getNpcId()))); break; case 3: PacketSendUtility.sendPacket(player, new SM_SELL_ITEM(player, targetObjectId)); break; case 4: // stigma PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 1)); break; case 5: // create legion if(MathUtil.isInRange(npc, player, 10)) // avoiding exploit with sending fake dialog_select packet { PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 2)); } else { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.LEGION_CREATE_TOO_FAR_FROM_NPC()); } break; case 6: // disband legion if(MathUtil.isInRange(npc, player, 10)) // avoiding exploit with sending fake dialog_select packet { sp.getLegionService().requestDisbandLegion(npc, player); } else { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.LEGION_DISPERSE_TOO_FAR_FROM_NPC()); } break; case 7: // recreate legion if(MathUtil.isInRange(npc, player, 10)) // voiding exploit with sending fake client dialog_select // packet { sp.getLegionService().recreateLegion(npc, player); } else { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.LEGION_DISPERSE_TOO_FAR_FROM_NPC()); } break; case 20: // warehouse if(MathUtil.isInRange(npc, player, 10)) // voiding exploit with sending fake client dialog_select // packet { if(!RestrictionsManager.canUseWarehouse(player)) return; PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 26)); sp.getWarehouseService().sendWarehouseInfo(player, true); } break; case 27: // Consign trade?? npc karinerk, koorunerk PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 13)); break; case 29: // soul healing RequestResponseHandler responseHandler = new RequestResponseHandler(npc){ @Override public void acceptRequest(Creature requester, Player responder) { Long lossexp = responder.getCommonData().getExpRecoverable(); if(player.getInventory().getKinahItem().getItemCount() > lossexp) { PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.EXP(String.valueOf(lossexp .intValue())));// TODO check // SM_SYSTEM_MESSAGE PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.SOUL_HEALED()); player.getCommonData().resetRecoverableExp(); player.getInventory().decreaseKinah(lossexp.intValue()); } // TODO not enought kinah message } @Override public void denyRequest(Creature requester, Player responder) { // no message } }; if(player.getCommonData().getExpRecoverable() > 0) { boolean result = player.getResponseRequester().putRequest(SM_QUESTION_WINDOW.STR_SOUL_HEALING, responseHandler); if(result) { PacketSendUtility.sendPacket(player, new SM_QUESTION_WINDOW( SM_QUESTION_WINDOW.STR_SOUL_HEALING, 0, String.valueOf(player.getCommonData() .getExpRecoverable()))); } } else PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.DONT_HAVE_RECOVERED_EXP()); break; case 35: // Godstone socketing PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 21)); break; case 36: // remove mana stone PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 20)); break; case 37: // modify appearance PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 19)); break; case 38: // flight and teleport sp.getTeleportService().showMap(player, targetObjectId, npc.getNpcId()); break; case 39: // improve extraction case 40: // learn tailoring armor smithing etc... sp.getCraftSkillUpdateService().learnSkill(player, npc); break; case 41: // expand cube sp.getCubeExpandService().expandCube(player, npc); break; case 42: sp.getWarehouseService().expandWarehouse(player, npc); break; case 47: // legion warehouse if(MathUtil.isInRange(npc, player, 10)) sp.getLegionService().openLegionWarehouse(player); break; case 50: // WTF??? Quest dialog packet break; case 52: if(MathUtil.isInRange(npc, player, 10)) // avoiding exploit with sending fake dialog_select packet { PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, 28)); } break; case 53: // coin reward PacketSendUtility.sendPacket(player, new SM_MESSAGE(0, null, "This feature is not available yet", ChatType.ANNOUNCEMENTS)); break; default: if(questId > 0) PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, dialogId, questId)); else PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(targetObjectId, dialogId)); break; } } @Override public void onAttack(Creature creature, int skillId, TYPE type, int damage) { if(getOwner().getLifeStats().isAlreadyDead()) return; super.onAttack(creature, skillId, type, damage); Npc npc = getOwner(); if(creature instanceof Player) if(sp.getQuestEngine().onAttack(new QuestEnv(npc, (Player) creature, 0, 0))) return; AI<?> ai = npc.getAi(); if(ai instanceof DummyAi) { log.warn("CHECKPOINT: npc attacked without ai " + npc.getObjectTemplate().getTemplateId()); return; } npc.getAggroList().addDamage(creature, damage); npc.getLifeStats().reduceHp(damage, creature); PacketSendUtility.broadcastPacket(npc, new SM_ATTACK_STATUS(npc, type, skillId, damage)); } @Override public void attackTarget(Creature target) { Npc npc = getOwner(); /** * Check all prerequisites */ if(npc == null || npc.getLifeStats().isAlreadyDead() || !npc.isSpawned()) return; if(!npc.canAttack()) return; AI<?> ai = npc.getAi(); NpcGameStats gameStats = npc.getGameStats(); if(target == null || target.getLifeStats().isAlreadyDead()) { ai.setAiState(AIState.THINKING); return; } /** * notify attack observers */ super.attackTarget(target); /** * Calculate and apply damage */ List<AttackResult> attackList = AttackUtil.calculateAttackResult(npc, target); int damage = 0; for(AttackResult result : attackList) { damage += result.getDamage(); } int attackType = 0; // TODO investigate attack types (0 or 1) PacketSendUtility.broadcastPacket(npc, new SM_ATTACK(npc, target, gameStats .getAttackCounter(), 274, attackType, attackList)); target.getController().onAttack(npc, damage); gameStats.increaseAttackCounter(); } /** * Schedule respawn of npc * In instances - no npc respawn */ public void scheduleRespawn() { if(getOwner().isInInstance()) return; int instanceId = getOwner().getInstanceId(); if(!getOwner().getSpawn().isNoRespawn(instanceId)) { Future<?> respawnTask = sp.getRespawnService().scheduleRespawnTask(getOwner()); addTask(TaskId.RESPAWN, respawnTask); } } }