/* * This program 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. * * This program 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 * this program. If not, see . */ package com.l2jserver.gameserver.model.actor; import static com.l2jserver.gameserver.ai.CtrlIntention.AI_INTENTION_ACTIVE; import java.util.Collection; import java.util.logging.Level; import javolution.util.FastList; import com.l2jserver.Config; import com.l2jserver.gameserver.SevenSigns; import com.l2jserver.gameserver.SevenSignsFestival; import com.l2jserver.gameserver.ThreadPoolManager; import com.l2jserver.gameserver.cache.HtmCache; import com.l2jserver.gameserver.datatables.ItemTable; import com.l2jserver.gameserver.handler.BypassHandler; import com.l2jserver.gameserver.handler.IBypassHandler; import com.l2jserver.gameserver.instancemanager.CastleManager; import com.l2jserver.gameserver.instancemanager.FortManager; import com.l2jserver.gameserver.instancemanager.TownManager; import com.l2jserver.gameserver.model.L2ItemInstance; import com.l2jserver.gameserver.model.L2NpcAIData; import com.l2jserver.gameserver.model.L2Object; import com.l2jserver.gameserver.model.L2Skill; import com.l2jserver.gameserver.model.L2Spawn; import com.l2jserver.gameserver.model.L2World; import com.l2jserver.gameserver.model.L2WorldRegion; import com.l2jserver.gameserver.model.actor.instance.L2ClanHallManagerInstance; import com.l2jserver.gameserver.model.actor.instance.L2DoormenInstance; import com.l2jserver.gameserver.model.actor.instance.L2FestivalGuideInstance; import com.l2jserver.gameserver.model.actor.instance.L2FishermanInstance; import com.l2jserver.gameserver.model.actor.instance.L2MerchantInstance; import com.l2jserver.gameserver.model.actor.instance.L2NpcInstance; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.actor.instance.L2TeleporterInstance; import com.l2jserver.gameserver.model.actor.instance.L2TrainerInstance; import com.l2jserver.gameserver.model.actor.instance.L2WarehouseInstance; import com.l2jserver.gameserver.model.actor.knownlist.NpcKnownList; import com.l2jserver.gameserver.model.actor.stat.NpcStat; import com.l2jserver.gameserver.model.actor.status.NpcStatus; import com.l2jserver.gameserver.model.entity.Castle; import com.l2jserver.gameserver.model.entity.Fort; import com.l2jserver.gameserver.model.olympiad.Olympiad; import com.l2jserver.gameserver.model.quest.Quest; import com.l2jserver.gameserver.model.zone.type.L2TownZone; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.AbstractNpcInfo; import com.l2jserver.gameserver.network.serverpackets.ActionFailed; import com.l2jserver.gameserver.network.serverpackets.ExChangeNpcState; import com.l2jserver.gameserver.network.serverpackets.MagicSkillUse; import com.l2jserver.gameserver.network.serverpackets.NpcHtmlMessage; import com.l2jserver.gameserver.network.serverpackets.ServerObjectInfo; import com.l2jserver.gameserver.network.serverpackets.SocialAction; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; import com.l2jserver.gameserver.taskmanager.DecayTaskManager; import com.l2jserver.gameserver.templates.chars.L2NpcTemplate; import com.l2jserver.gameserver.templates.chars.L2NpcTemplate.AIType; import com.l2jserver.gameserver.templates.item.L2Item; import com.l2jserver.gameserver.templates.item.L2Weapon; import com.l2jserver.gameserver.util.Broadcast; import com.l2jserver.util.Rnd; import com.l2jserver.util.StringUtil; /** * This class represents a Non-Player-Character in the world. It can be a monster or a friendly character. * It also uses a template to fetch some static values. The templates are hardcoded in the client, so we can rely on them.

* * L2Character :

*
  • L2Attackable
  • *
  • L2BoxInstance
  • *
  • L2FolkInstance
  • * * @version $Revision: 1.32.2.7.2.24 $ $Date: 2005/04/11 10:06:09 $ */ public class L2Npc extends L2Character { //private static Logger _log = Logger.getLogger(L2NpcInstance.class.getName()); /** The interaction distance of the L2NpcInstance(is used as offset in MovetoLocation method) */ public static final int INTERACTION_DISTANCE = 150; /** The L2Spawn object that manage this L2NpcInstance */ private L2Spawn _spawn; /** The flag to specify if this L2NpcInstance is busy */ private boolean _isBusy = false; /** The busy message for this L2NpcInstance */ private String _busyMessage = ""; /** True if endDecayTask has already been called */ volatile boolean _isDecayed = false; /** The castle index in the array of L2Castle this L2NpcInstance belongs to */ private int _castleIndex = -2; /** The fortress index in the array of L2Fort this L2NpcInstance belongs to */ private int _fortIndex = -2; public boolean isEventMob = false; private boolean _isInTown = false; /** True if this L2Npc is autoattackable **/ private boolean _isAutoAttackable = false; /** Time of last social packet broadcast*/ private long _lastSocialBroadcast = 0; /** Minimum interval between social packets*/ private int _minimalSocialInterval = 6000; protected RandomAnimationTask _rAniTask = null; private int _currentLHandId; // normally this shouldn't change from the template, but there exist exceptions private int _currentRHandId; // normally this shouldn't change from the template, but there exist exceptions private int _currentEnchant; // normally this shouldn't change from the template, but there exist exceptions private double _currentCollisionHeight; // used for npc grow effect skills private double _currentCollisionRadius; // used for npc grow effect skills public boolean _soulshotcharged = false; public boolean _spiritshotcharged = false; private int _soulshotamount = 0; private int _spiritshotamount = 0; public boolean _ssrecharged = true; public boolean _spsrecharged = true; protected boolean _isHideName = false; private int _displayEffect = 0; //AI Recall public int getSoulShot() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getSoulShot(); } public int getSpiritShot() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getSpiritShot(); } public int getSoulShotChance() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getSoulShotChance(); } public int getSpiritShotChance() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getSpiritShotChance(); } public boolean useSoulShot() { if(_soulshotcharged) return true; if(_ssrecharged) { _soulshotamount = getSoulShot(); _ssrecharged = false; } else if (_soulshotamount>0) { if (Rnd.get(100) <= getSoulShotChance()) { _soulshotamount = _soulshotamount - 1; Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2154, 1, 0, 0), 360000); _soulshotcharged = true; } } else return false; return _soulshotcharged; } public boolean useSpiritShot() { if(_spiritshotcharged) return true; else { //_spiritshotcharged = false; if(_spsrecharged) { _spiritshotamount = getSpiritShot(); _spsrecharged = false; } else if (_spiritshotamount>0) { if (Rnd.get(100) <= getSpiritShotChance()) { _spiritshotamount = _spiritshotamount - 1; Broadcast.toSelfAndKnownPlayersInRadius(this, new MagicSkillUse(this, this, 2061, 1, 0, 0), 360000); _spiritshotcharged = true; } } else return false; } return _spiritshotcharged; } public int getEnemyRange() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getEnemyRange(); } public String getEnemyClan() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getEnemyClan(); } public int getClanRange() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getClanRange(); } public String getClan() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getClan(); } // GET THE PRIMARY ATTACK public int getPrimaryAttack() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getPrimaryAttack(); } public int getSkillChance() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getSkillChance(); } public int getCanMove() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getCanMove(); } public int getIsChaos() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getIsChaos(); } public int getCanDodge() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getDodge(); } public int getSSkillChance() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getShortRangeChance(); } public int getLSkillChance() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getLongRangeChance(); } public int getSwitchRangeChance() { L2NpcAIData AI = getTemplate().getAIDataStatic(); return AI.getSwitchRangeChance(); } public boolean hasLSkill() { L2NpcAIData AI = getTemplate().getAIDataStatic(); if (AI.getLongRangeSkill() == 0) return false; else return true; } public boolean hasSSkill() { L2NpcAIData AI = getTemplate().getAIDataStatic(); if (AI.getShortRangeSkill() == 0) return false; else return true; } public FastList getLrangeSkill() { FastList skilldata = new FastList (); boolean hasLrange = false; L2NpcAIData AI = getTemplate().getAIDataStatic(); if (AI == null || AI.getLongRangeSkill() == 0) return null; switch (AI.getLongRangeSkill()) { case -1: { L2Skill[] skills = null; skills = getAllSkills(); if (skills != null) { for (L2Skill sk: skills) { if (sk == null || sk.isPassive() || sk.getTargetType() == L2Skill.SkillTargetType.TARGET_SELF) continue; if (sk.getCastRange() >= 200) { skilldata.add(sk); hasLrange = true; } } } break; } case 1: { if (getTemplate()._universalskills != null) { for (L2Skill sk: getTemplate()._universalskills) { if (sk.getCastRange() >= 200) { skilldata.add(sk); hasLrange = true; } } } break; } default: { for (L2Skill sk: getAllSkills()) { if (sk.getId() == AI.getLongRangeSkill()) { skilldata.add(sk); hasLrange = true; } } } } return (hasLrange ? skilldata : null); } public FastList getSrangeSkill() { FastList skilldata = new FastList (); boolean hasSrange = false; L2NpcAIData AI = getTemplate().getAIDataStatic(); if (AI == null || AI.getShortRangeSkill() == 0) return null; switch (AI.getShortRangeSkill()) { case -1: { L2Skill[] skills = null; skills = getAllSkills(); if (skills != null) { for (L2Skill sk: skills) { if (sk == null || sk.isPassive() || sk.getTargetType() == L2Skill.SkillTargetType.TARGET_SELF) continue; if (sk.getCastRange() <= 200) { skilldata.add(sk); hasSrange = true; } } } break; } case 1: { if (getTemplate()._universalskills != null) { for (L2Skill sk: getTemplate()._universalskills) { if (sk.getCastRange() <= 200) { skilldata.add(sk); hasSrange = true; } } } break; } default: { for (L2Skill sk: getAllSkills()) { if (sk.getId() == AI.getShortRangeSkill()) { skilldata.add(sk); hasSrange = true; } } } } return (hasSrange ? skilldata : null); } /** Task launching the function onRandomAnimation() */ protected class RandomAnimationTask implements Runnable { public void run() { try { if (this != _rAniTask) return; // Shouldn't happen, but who knows... just to make sure every active npc has only one timer. if (isMob()) { // Cancel further animation timers until intention is changed to ACTIVE again. if (getAI().getIntention() != AI_INTENTION_ACTIVE) return; } else { if (!isInActiveRegion()) // NPCs in inactive region don't run this task return; } if (!(isDead() || isStunned() || isSleeping() || isParalyzed())) onRandomAnimation(Rnd.get(2, 3)); startRandomAnimationTimer(); } catch (Exception e) { _log.log(Level.SEVERE, "", e); } } } /** * Send a packet SocialAction to all L2PcInstance in the _KnownPlayers of the L2NpcInstance and create a new RandomAnimation Task.

    */ public void onRandomAnimation(int animationId) { // Send a packet SocialAction to all L2PcInstance in the _KnownPlayers of the L2NpcInstance long now = System.currentTimeMillis(); if (now - _lastSocialBroadcast > _minimalSocialInterval) { _lastSocialBroadcast = now; broadcastPacket(new SocialAction(getObjectId(), animationId)); } } /** * Create a RandomAnimation Task that will be launched after the calculated delay.

    */ public void startRandomAnimationTimer() { if (!hasRandomAnimation()) return; int minWait = isMob() ? Config.MIN_MONSTER_ANIMATION : Config.MIN_NPC_ANIMATION; int maxWait = isMob() ? Config.MAX_MONSTER_ANIMATION : Config.MAX_NPC_ANIMATION; // Calculate the delay before the next animation int interval = Rnd.get(minWait, maxWait) * 1000; // Create a RandomAnimation Task that will be launched after the calculated delay _rAniTask = new RandomAnimationTask(); ThreadPoolManager.getInstance().scheduleGeneral(_rAniTask, interval); } /** * Check if the server allows Random Animation.

    */ public boolean hasRandomAnimation() { return (Config.MAX_NPC_ANIMATION > 0 && !getAiType().equals(AIType.CORPSE)); } /** * Constructor of L2NpcInstance (use L2Character constructor).

    * * Actions :

    *
  • Call the L2Character constructor to set the _template of the L2Character (copy skills from template to object and link _calculators to NPC_STD_CALCULATOR)
  • *
  • Set the name of the L2Character
  • *
  • Create a RandomAnimation Task that will be launched after the calculated delay if the server allow it


  • * * @param objectId Identifier of the object to initialized * @param template The L2NpcTemplate to apply to the NPC * */ public L2Npc(int objectId, L2NpcTemplate template) { // Call the L2Character constructor to set the _template of the L2Character, copy skills from template to object // and link _calculators to NPC_STD_CALCULATOR super(objectId, template); setInstanceType(InstanceType.L2Npc); initCharStatusUpdateValues(); // initialize the "current" equipment _currentLHandId = getTemplate().lhand; _currentRHandId = getTemplate().rhand; _currentEnchant = Config.ENABLE_RANDOM_ENCHANT_EFFECT ? Rnd.get(4,21) : getTemplate().enchantEffect; // initialize the "current" collisions _currentCollisionHeight = getTemplate().fCollisionHeight; _currentCollisionRadius = getTemplate().fCollisionRadius; if (template == null) { _log.severe("No template for Npc. Please check your datapack is setup correctly."); return; } // Set the name of the L2Character setName(template.name); } @Override public NpcKnownList getKnownList() { return (NpcKnownList) super.getKnownList(); } @Override public void initKnownList() { setKnownList(new NpcKnownList(this)); } @Override public NpcStat getStat() { return (NpcStat) super.getStat(); } @Override public void initCharStat() { setStat(new NpcStat(this)); } @Override public NpcStatus getStatus() { return (NpcStatus) super.getStatus(); } @Override public void initCharStatus() { setStatus(new NpcStatus(this)); } /** Return the L2NpcTemplate of the L2NpcInstance. */ @Override public final L2NpcTemplate getTemplate() { return (L2NpcTemplate) super.getTemplate(); } /** * Return the generic Identifier of this L2NpcInstance contained in the L2NpcTemplate.

    */ public int getNpcId() { return getTemplate().npcId; } @Override public boolean isAttackable() { return Config.ALT_ATTACKABLE_NPCS; } /** * Return the faction Identifier of this L2NpcInstance contained in the L2NpcTemplate.

    * * Concept :

    * If a NPC belows to a Faction, other NPC of the faction inside the Faction range will help it if it's attacked

    * */ //@Deprecated public final String getFactionId() { return getClan(); } /** * Return the Level of this L2NpcInstance contained in the L2NpcTemplate.

    */ @Override public final int getLevel() { return getTemplate().level; } /** * Return True if the L2NpcInstance is agressive (ex : L2MonsterInstance in function of aggroRange).

    */ public boolean isAggressive() { return false; } /** * Return the Aggro Range of this L2NpcInstance contained in the L2NpcTemplate.

    */ public int getAggroRange() { return getTemplate().aggroRange; } /** * Return the Faction Range of this L2NpcInstance contained in the L2NpcTemplate.

    */ //@Deprecated public int getFactionRange() { return getClanRange(); } /** * Return True if this L2NpcInstance is undead in function of the L2NpcTemplate.

    */ @Override public boolean isUndead() { return getTemplate().isUndead(); } /** * Send a packet NpcInfo with state of abnormal effect to all L2PcInstance in the _KnownPlayers of the L2NpcInstance.

    */ @Override public void updateAbnormalEffect() { // Send a Server->Client packet NpcInfo with state of abnormal effect to all L2PcInstance in the _KnownPlayers of the L2NpcInstance Collection plrs = getKnownList().getKnownPlayers().values(); //synchronized (getKnownList().getKnownPlayers()) { for (L2PcInstance player : plrs) { if (player == null) continue; if (getRunSpeed() == 0) player.sendPacket(new ServerObjectInfo(this, player)); else player.sendPacket(new AbstractNpcInfo.NpcInfo(this, player)); } } } /** * Return the distance under which the object must be add to _knownObject in * function of the object type.
    *
    * * Values :
    *
    *
  • object is a L2FolkInstance : 0 (don't remember it)
  • *
  • object is a L2Character : 0 (don't remember it)
  • *
  • object is a L2PlayableInstance : 1500
  • *
  • others : 500
  • *
    *
    * * Override in :
    *
    *
  • L2Attackable
  • *
    *
    * * @param object * The Object to add to _knownObject * */ public int getDistanceToWatchObject(L2Object object) { if (object instanceof L2FestivalGuideInstance) return 10000; if (object instanceof L2NpcInstance || !(object instanceof L2Character)) return 0; if (object instanceof L2Playable) return 1500; return 500; } /** * Return the distance after which the object must be remove from _knownObject in function of the object type.

    * * Values :

    *
  • object is not a L2Character : 0 (don't remember it)
  • *
  • object is a L2FolkInstance : 0 (don't remember it)
  • *
  • object is a L2PlayableInstance : 3000
  • *
  • others : 1000


  • * * Overridden in :

    *
  • L2Attackable


  • * * @param object The Object to remove from _knownObject * */ public int getDistanceToForgetObject(L2Object object) { return 2 * getDistanceToWatchObject(object); } /** * Return False.

    * * Overridden in :

    *
  • L2MonsterInstance : Check if the attacker is not another L2MonsterInstance
  • *
  • L2PcInstance


  • */ @Override public boolean isAutoAttackable(L2Character attacker) { return _isAutoAttackable; } public void setAutoAttackable(boolean flag) { _isAutoAttackable = flag; } /** * Return the Identifier of the item in the left hand of this L2NpcInstance contained in the L2NpcTemplate.

    */ public int getLeftHandItem() { return _currentLHandId; } /** * Return the Identifier of the item in the right hand of this L2NpcInstance contained in the L2NpcTemplate.

    */ public int getRightHandItem() { return _currentRHandId; } public int getEnchantEffect() { return _currentEnchant; } /** * Return the busy status of this L2NpcInstance.

    */ public final boolean isBusy() { return _isBusy; } /** * Set the busy status of this L2NpcInstance.

    */ public void setBusy(boolean isBusy) { _isBusy = isBusy; } /** * Return the busy message of this L2NpcInstance.

    */ public final String getBusyMessage() { return _busyMessage; } /** * Set the busy message of this L2NpcInstance.

    */ public void setBusyMessage(String message) { _busyMessage = message; } /** * Return true if this L2Npc instance can be warehouse manager.

    */ public boolean isWarehouse() { return false; } public boolean canTarget(L2PcInstance player) { if (player.isOutOfControl()) { player.sendPacket(ActionFailed.STATIC_PACKET); return false; } if (player.isLockedTarget() && player.getLockedTarget() != this) { player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.FAILED_CHANGE_TARGET)); player.sendPacket(ActionFailed.STATIC_PACKET); return false; } // TODO: More checks... return true; } public boolean canInteract(L2PcInstance player) { // TODO: NPC busy check etc... if (player.isCastingNow() || player.isCastingSimultaneouslyNow()) return false; if (player.isDead() || player.isFakeDeath()) return false; if (player.isSitting()) return false; if (player.getPrivateStoreType() != 0) return false; if (!isInsideRadius(player, INTERACTION_DISTANCE, true, false)) return false; if (player.getInstanceId() != getInstanceId() && player.getInstanceId() != -1) return false; return true; } /** Return the L2Castle this L2NpcInstance belongs to. */ public final Castle getCastle() { // Get castle this NPC belongs to (excluding L2Attackable) if (_castleIndex < 0) { L2TownZone town = TownManager.getTown(getX(), getY(), getZ()); if (town != null) _castleIndex = CastleManager.getInstance().getCastleIndex(town.getTaxById()); if (_castleIndex < 0) { _castleIndex = CastleManager.getInstance().findNearestCastleIndex(this); } else _isInTown = true; // Npc was spawned in town } if (_castleIndex < 0) return null; return CastleManager.getInstance().getCastles().get(_castleIndex); } /** * Return closest castle in defined distance * @param maxDistance long * @return Castle */ public final Castle getCastle(long maxDistance) { int index = CastleManager.getInstance().findNearestCastleIndex(this, maxDistance); if (index < 0) return null; return CastleManager.getInstance().getCastles().get(index); } /** Return the L2Fort this L2NpcInstance belongs to. */ public final Fort getFort() { // Get Fort this NPC belongs to (excluding L2Attackable) if (_fortIndex < 0) { Fort fort = FortManager.getInstance().getFort(getX(), getY(), getZ()); if (fort != null) _fortIndex = FortManager.getInstance().getFortIndex(fort.getFortId()); if (_fortIndex < 0) _fortIndex = FortManager.getInstance().findNearestFortIndex(this); } if (_fortIndex < 0) return null; return FortManager.getInstance().getForts().get(_fortIndex); } /** * Return closest Fort in defined distance * @param maxDistance long * @return Fort */ public final Fort getFort(long maxDistance) { int index = FortManager.getInstance().findNearestFortIndex(this, maxDistance); if (index < 0) return null; return FortManager.getInstance().getForts().get(index); } public final boolean getIsInTown() { if (_castleIndex < 0) getCastle(); return _isInTown; } /** * Open a quest or chat window on client with the text of the L2NpcInstance in function of the command.

    * * Example of use :

    *
  • Client packet : RequestBypassToServer


  • * * @param command The command string received from client * */ public void onBypassFeedback(L2PcInstance player, String command) { //if (canInteract(player)) { if (isBusy() && getBusyMessage().length() > 0) { player.sendPacket(ActionFailed.STATIC_PACKET); NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); html.setFile(player.getHtmlPrefix(), "data/html/npcbusy.htm"); html.replace("%busymessage%", getBusyMessage()); html.replace("%npcname%", getName()); html.replace("%playername%", player.getName()); player.sendPacket(html); } else { IBypassHandler handler = BypassHandler.getInstance().getBypassHandler(command); if (handler != null) handler.useBypass(command, player, this); else _log.info(getClass().getSimpleName()+": Unknown NPC bypass: \""+command+"\" NpcId: "+getNpcId()); } } } /** * Return null (regular NPCs don't have weapons instancies).

    */ @Override public L2ItemInstance getActiveWeaponInstance() { // regular NPCs dont have weapons instancies return null; } /** * Return the weapon item equiped in the right hand of the L2NpcInstance or null.

    */ @Override public L2Weapon getActiveWeaponItem() { // Get the weapon identifier equiped in the right hand of the L2NpcInstance int weaponId = getTemplate().rhand; if (weaponId < 1) return null; // Get the weapon item equiped in the right hand of the L2NpcInstance L2Item item = ItemTable.getInstance().getTemplate(getTemplate().rhand); if (!(item instanceof L2Weapon)) return null; return (L2Weapon) item; } /** * Return null (regular NPCs don't have weapons instancies).

    */ @Override public L2ItemInstance getSecondaryWeaponInstance() { // regular NPCs dont have weapons instancies return null; } /** * Return the weapon item equiped in the left hand of the L2NpcInstance or null.

    */ @Override public L2Weapon getSecondaryWeaponItem() { // Get the weapon identifier equiped in the right hand of the L2NpcInstance int weaponId = getTemplate().lhand; if (weaponId < 1) return null; // Get the weapon item equiped in the right hand of the L2NpcInstance L2Item item = ItemTable.getInstance().getTemplate(getTemplate().lhand); if (!(item instanceof L2Weapon)) return null; return (L2Weapon) item; } /** * Send a Server->Client packet NpcHtmlMessage to the L2PcInstance in order to display the message of the L2NpcInstance.

    * * @param player The L2PcInstance who talks with the L2NpcInstance * @param content The text of the L2NpcMessage * */ public void insertObjectIdAndShowChatWindow(L2PcInstance player, String content) { // Send a Server->Client packet NpcHtmlMessage to the L2PcInstance in order to display the message of the L2NpcInstance content = content.replaceAll("%objectId%", String.valueOf(getObjectId())); NpcHtmlMessage npcReply = new NpcHtmlMessage(getObjectId()); npcReply.setHtml(content); player.sendPacket(npcReply); } /** * Return the pathfile of the selected HTML file in function of the npcId and of the page number.

    * * Format of the pathfile :

    *
  • if the file exists on the server (page number = 0) : data/html/default/12006.htm (npcId-page number)
  • *
  • if the file exists on the server (page number > 0) : data/html/default/12006-1.htm (npcId-page number)
  • *
  • if the file doesn't exist on the server : data/html/npcdefault.htm (message : "I have nothing to say to you")


  • * * Overridden in :

    *
  • L2GuardInstance : Set the pathfile to data/html/guard/12006-1.htm (npcId-page number)


  • * * @param npcId The Identifier of the L2NpcInstance whose text must be display * @param val The number of the page to display * */ public String getHtmlPath(int npcId, int val) { String pom = ""; if (val == 0) pom = "" + npcId; else pom = npcId + "-" + val; String temp = "data/html/default/" + pom + ".htm"; if (!Config.LAZY_CACHE) { // If not running lazy cache the file must be in the cache or it doesnt exist if (HtmCache.getInstance().contains(temp)) return temp; } else { if (HtmCache.getInstance().isLoadable(temp)) return temp; } // If the file is not found, the standard message "I have nothing to say to you" is returned return "data/html/npcdefault.htm"; } public void showChatWindow(L2PcInstance player) { showChatWindow(player, 0); } /** * Returns true if html exists * @param player * @param type * @return boolean */ private boolean showPkDenyChatWindow(L2PcInstance player, String type) { String html = HtmCache.getInstance().getHtm(player.getHtmlPrefix(), "data/html/" + type + "/" + getNpcId() + "-pk.htm"); if (html != null) { NpcHtmlMessage pkDenyMsg = new NpcHtmlMessage(getObjectId()); pkDenyMsg.setHtml(html); player.sendPacket(pkDenyMsg); player.sendPacket(ActionFailed.STATIC_PACKET); return true; } return false; } /** * Open a chat window on client with the text of the L2NpcInstance.

    * * Actions :

    *
  • Get the text of the selected HTML file in function of the npcId and of the page number
  • *
  • Send a Server->Client NpcHtmlMessage containing the text of the L2NpcInstance to the L2PcInstance
  • *
  • Send a Server->Client ActionFailed to the L2PcInstance in order to avoid that the client wait another packet

  • * * @param player The L2PcInstance that talk with the L2NpcInstance * @param val The number of the page of the L2NpcInstance to display * */ public void showChatWindow(L2PcInstance player, int val) { if (player.isCursedWeaponEquipped() && (!(player.getTarget() instanceof L2ClanHallManagerInstance) || !(player.getTarget() instanceof L2DoormenInstance))) { player.setTarget(player); return; } if (player.getKarma() > 0) { if (!Config.ALT_GAME_KARMA_PLAYER_CAN_SHOP && this instanceof L2MerchantInstance) { if (showPkDenyChatWindow(player, "merchant")) return; } else if (!Config.ALT_GAME_KARMA_PLAYER_CAN_USE_GK && this instanceof L2TeleporterInstance) { if (showPkDenyChatWindow(player, "teleporter")) return; } else if (!Config.ALT_GAME_KARMA_PLAYER_CAN_USE_WAREHOUSE && this instanceof L2WarehouseInstance) { if (showPkDenyChatWindow(player, "warehouse")) return; } else if (!Config.ALT_GAME_KARMA_PLAYER_CAN_SHOP && this instanceof L2FishermanInstance) { if (showPkDenyChatWindow(player, "fisherman")) return; } } if ("L2Auctioneer".equals(getTemplate().type) && val == 0) return; int npcId = getTemplate().npcId; /* For use with Seven Signs implementation */ String filename = SevenSigns.SEVEN_SIGNS_HTML_PATH; int sealAvariceOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_AVARICE); int sealGnosisOwner = SevenSigns.getInstance().getSealOwner(SevenSigns.SEAL_GNOSIS); int playerCabal = SevenSigns.getInstance().getPlayerCabal(player.getObjectId()); int compWinner = SevenSigns.getInstance().getCabalHighestScore(); switch (npcId) { case 31127: // case 31128: // case 31129: // Dawn Festival Guides case 31130: // case 31131: // filename += "festival/dawn_guide.htm"; break; case 31137: // case 31138: // case 31139: // Dusk Festival Guides case 31140: // case 31141: // filename += "festival/dusk_guide.htm"; break; case 31092: // Black Marketeer of Mammon filename += "blkmrkt_1.htm"; break; case 31113: // Merchant of Mammon if (Config.ALT_STRICT_SEVENSIGNS) { switch (compWinner) { case SevenSigns.CABAL_DAWN: if (playerCabal != compWinner || playerCabal != sealAvariceOwner) { player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CAN_BE_USED_BY_DAWN)); player.sendPacket(ActionFailed.STATIC_PACKET); return; } break; case SevenSigns.CABAL_DUSK: if (playerCabal != compWinner || playerCabal != sealAvariceOwner) { player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CAN_BE_USED_BY_DUSK)); player.sendPacket(ActionFailed.STATIC_PACKET); return; } break; default: player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.QUEST_EVENT_PERIOD)); return; } } filename += "mammmerch_1.htm"; break; case 31126: // Blacksmith of Mammon if (Config.ALT_STRICT_SEVENSIGNS) { switch (compWinner) { case SevenSigns.CABAL_DAWN: if (playerCabal != compWinner || playerCabal != sealGnosisOwner) { player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CAN_BE_USED_BY_DAWN)); player.sendPacket(ActionFailed.STATIC_PACKET); return; } break; case SevenSigns.CABAL_DUSK: if (playerCabal != compWinner || playerCabal != sealGnosisOwner) { player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CAN_BE_USED_BY_DUSK)); player.sendPacket(ActionFailed.STATIC_PACKET); return; } break; default: player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.QUEST_EVENT_PERIOD)); return; } } filename += "mammblack_1.htm"; break; case 31132: case 31133: case 31134: case 31135: case 31136: // Festival Witches case 31142: case 31143: case 31144: case 31145: case 31146: filename += "festival/festival_witch.htm"; break; case 31688: if (player.isNoble()) filename = Olympiad.OLYMPIAD_HTML_PATH + "noble_main.htm"; else filename = (getHtmlPath(npcId, val)); break; case 31690: case 31769: case 31770: case 31771: case 31772: if (player.isHero() || player.isNoble()) filename = Olympiad.OLYMPIAD_HTML_PATH + "hero_main.htm"; else filename = (getHtmlPath(npcId, val)); break; case 36402: if (player.olyBuff > 0) filename = (player.olyBuff == 5 ? Olympiad.OLYMPIAD_HTML_PATH + "olympiad_buffs.htm" : Olympiad.OLYMPIAD_HTML_PATH + "olympiad_5buffs.htm"); else filename = Olympiad.OLYMPIAD_HTML_PATH + "olympiad_nobuffs.htm"; break; case 30298: // Blacksmith Pinter if(player.isAcademyMember()) filename = (getHtmlPath(npcId, 1)); else filename = (getHtmlPath(npcId, 0)); break; default: if (npcId >= 31865 && npcId <= 31918) { if (val == 0 ) filename += "rift/GuardianOfBorder.htm"; else filename += "rift/GuardianOfBorder-" + val + ".htm"; break; } if ((npcId >= 31093 && npcId <= 31094) || (npcId >= 31172 && npcId <= 31201) || (npcId >= 31239 && npcId <= 31254)) return; // Get the text of the selected HTML file in function of the npcId and of the page number filename = (getHtmlPath(npcId, val)); break; } // Send a Server->Client NpcHtmlMessage containing the text of the L2NpcInstance to the L2PcInstance NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); html.setFile(player.getHtmlPrefix(), filename); if (this instanceof L2MerchantInstance) { if (Config.LIST_PET_RENT_NPC.contains(npcId)) html.replace("_Quest", "_RentPet\">Rent Pet
    Client ActionFailed to the L2PcInstance in order to avoid that the client wait another packet player.sendPacket(ActionFailed.STATIC_PACKET); } /** * Open a chat window on client with the text specified by the given file name and path,
    * relative to the datapack root. *

    * Added by Tempy * @param player The L2PcInstance that talk with the L2NpcInstance * @param filename The filename that contains the text to send * */ public void showChatWindow(L2PcInstance player, String filename) { // Send a Server->Client NpcHtmlMessage containing the text of the L2NpcInstance to the L2PcInstance NpcHtmlMessage html = new NpcHtmlMessage(getObjectId()); html.setFile(player.getHtmlPrefix(), filename); html.replace("%objectId%", String.valueOf(getObjectId())); player.sendPacket(html); // Send a Server->Client ActionFailed to the L2PcInstance in order to avoid that the client wait another packet player.sendPacket(ActionFailed.STATIC_PACKET); } /** * Return the Exp Reward of this L2NpcInstance contained in the L2NpcTemplate (modified by RATE_XP).

    */ public int getExpReward() { return (int) (getTemplate().rewardExp * Config.RATE_XP); } /** * Return the SP Reward of this L2NpcInstance contained in the L2NpcTemplate (modified by RATE_SP).

    */ public int getSpReward() { return (int) (getTemplate().rewardSp * Config.RATE_SP); } /** * Kill the L2NpcInstance (the corpse disappeared after 7 seconds).

    * * Actions :

    *
  • Create a DecayTask to remove the corpse of the L2NpcInstance after 7 seconds
  • *
  • Set target to null and cancel Attack or Cast
  • *
  • Stop movement
  • *
  • Stop HP/MP/CP Regeneration task
  • *
  • Stop all active skills effects in progress on the L2Character
  • *
  • Send the Server->Client packet StatusUpdate with current HP and MP to all other L2PcInstance to inform
  • *
  • Notify L2Character AI


  • * * Overridden in :

    *
  • L2Attackable


  • * * @param killer The L2Character who killed it * */ @Override public boolean doDie(L2Character killer) { if (!super.doDie(killer)) return false; // normally this wouldn't really be needed, but for those few exceptions, // we do need to reset the weapons back to the initial templated weapon. _currentLHandId = getTemplate().lhand; _currentRHandId = getTemplate().rhand; _currentCollisionHeight = getTemplate().fCollisionHeight; _currentCollisionRadius = getTemplate().fCollisionRadius; DecayTaskManager.getInstance().addDecayTask(this); return true; } /** * Set the spawn of the L2NpcInstance.

    * * @param spawn The L2Spawn that manage the L2NpcInstance * */ public void setSpawn(L2Spawn spawn) { _spawn = spawn; } @Override public void onSpawn() { super.onSpawn(); if (getTemplate().getEventQuests(Quest.QuestEventType.ON_SPAWN) != null) for (Quest quest : getTemplate().getEventQuests(Quest.QuestEventType.ON_SPAWN)) quest.notifySpawn(this); } /** * Remove the L2NpcInstance from the world and update its spawn object (for a complete removal use the deleteMe method).

    * * Actions :

    *
  • Remove the L2NpcInstance from the world when the decay task is launched
  • *
  • Decrease its spawn counter
  • *
  • Manage Siege task (killFlag, killCT)


  • * * Caution : This method DOESN'T REMOVE the object from _allObjects of L2World
    * Caution : This method DOESN'T SEND Server->Client packets to players

    * */ @Override public void onDecay() { if (isDecayed()) return; setDecayed(true); // Remove the L2NpcInstance from the world when the decay task is launched super.onDecay(); // Decrease its spawn counter if (_spawn != null) _spawn.decreaseCount(this); } /** * Remove PROPERLY the L2NpcInstance from the world.

    * * Actions :

    *
  • Remove the L2NpcInstance from the world and update its spawn object
  • *
  • Remove all L2Object from _knownObjects and _knownPlayer of the L2NpcInstance then cancel Attack or Cast and notify AI
  • *
  • Remove L2Object object from _allObjects of L2World


  • * * Caution : This method DOESN'T SEND Server->Client packets to players

    * */ @Override public void deleteMe() { L2WorldRegion oldRegion = getWorldRegion(); try { onDecay(); } catch (Exception e) { _log.log(Level.SEVERE, "Failed decayMe().", e); } try { if (_fusionSkill != null) abortCast(); for (L2Character character : getKnownList().getKnownCharacters()) if (character.getFusionSkill() != null && character.getFusionSkill().getTarget() == this) character.abortCast(); } catch (Exception e) { _log.log(Level.SEVERE, "deleteMe()", e); } if (oldRegion != null) oldRegion.removeFromZones(this); // Remove all L2Object from _knownObjects and _knownPlayer of the L2Character then cancel Attak or Cast and notify AI try { getKnownList().removeAllKnownObjects(); } catch (Exception e) { _log.log(Level.SEVERE, "Failed removing cleaning knownlist.", e); } // Remove L2Object object from _allObjects of L2World L2World.getInstance().removeObject(this); super.deleteMe(); } /** * Return the L2Spawn object that manage this L2NpcInstance.

    */ public L2Spawn getSpawn() { return _spawn; } @Override public String toString() { return getClass().getSimpleName()+":"+getTemplate().name+"("+getNpcId()+")"+"["+getObjectId()+"]"; } public boolean isDecayed() { return _isDecayed; } public void setDecayed(boolean decayed) { _isDecayed = decayed; } public void endDecayTask() { if (!isDecayed()) { DecayTaskManager.getInstance().cancelDecayTask(this); onDecay(); } } public boolean isMob() // rather delete this check { return false; // This means we use MAX_NPC_ANIMATION instead of MAX_MONSTER_ANIMATION } // Two functions to change the appearance of the equipped weapons on the NPC // This is only useful for a few NPCs and is most likely going to be called from AI public void setLHandId(int newWeaponId) { _currentLHandId = newWeaponId; updateAbnormalEffect(); } public void setRHandId(int newWeaponId) { _currentRHandId = newWeaponId; updateAbnormalEffect(); } public void setLRHandId(int newLWeaponId, int newRWeaponId) { _currentRHandId = newRWeaponId; _currentLHandId = newLWeaponId; updateAbnormalEffect(); } public void setEnchant(int newEnchantValue) { _currentEnchant = newEnchantValue; updateAbnormalEffect(); } public void setHideName(boolean val) { _isHideName = val; } public boolean isHideName() { return _isHideName; } public void setCollisionHeight(double height) { _currentCollisionHeight = height; } public void setCollisionRadius(double radius) { _currentCollisionRadius = radius; } public double getCollisionHeight() { return _currentCollisionHeight; } public double getCollisionRadius() { return _currentCollisionRadius; } @Override public void sendInfo(L2PcInstance activeChar) { if (Config.CHECK_KNOWN && activeChar.isGM()) activeChar.sendMessage("Added NPC: "+getName()); if (getRunSpeed() == 0) activeChar.sendPacket(new ServerObjectInfo(this, activeChar)); else activeChar.sendPacket(new AbstractNpcInfo.NpcInfo(this, activeChar)); } public void showNoTeachHtml(L2PcInstance player) { int npcId = getNpcId(); String html = ""; if (this instanceof L2WarehouseInstance) html = HtmCache.getInstance().getHtm(player.getHtmlPrefix(), "data/html/warehouse/" + npcId + "-noteach.htm"); else if (this instanceof L2TrainerInstance) html = HtmCache.getInstance().getHtm(player.getHtmlPrefix(), "data/html/trainer/" + npcId + "-noteach.htm"); if (html == null) { _log.warning("Npc " + npcId + " missing noTeach html!"); NpcHtmlMessage msg = new NpcHtmlMessage(getObjectId()); final String sb = StringUtil.concat( "" + "I cannot teach you any skills.
    You must find your current class teachers.", "" ); msg.setHtml(sb); player.sendPacket(msg); return; } else { NpcHtmlMessage noTeachMsg = new NpcHtmlMessage(getObjectId()); noTeachMsg.setHtml(html); noTeachMsg.replace("%objectId%", String.valueOf(getObjectId())); player.sendPacket(noTeachMsg); } } public L2Npc scheduleDespawn(long delay) { ThreadPoolManager.getInstance().scheduleGeneral(this.new DespawnTask(), delay); return this; } private class DespawnTask implements Runnable { @Override public void run() { if (!L2Npc.this.isDecayed()) L2Npc.this.deleteMe(); } } @Override protected final void notifyQuestEventSkillFinished(L2Skill skill, L2Object target) { try { if (getTemplate().getEventQuests(Quest.QuestEventType.ON_SPELL_FINISHED) != null) { L2PcInstance player = null; if (target != null) player = target.getActingPlayer(); for (Quest quest : getTemplate().getEventQuests(Quest.QuestEventType.ON_SPELL_FINISHED)) { quest.notifySpellFinished(this, player, skill); } } } catch (Exception e) { _log.log(Level.SEVERE, "", e); } } /* (non-Javadoc) * @see com.l2jserver.gameserver.model.actor.L2Character#isMovementDisabled() */ @Override public boolean isMovementDisabled() { return super.isMovementDisabled() || getCanMove() == 0 || getAiType().equals(AIType.CORPSE); } public AIType getAiType() { return getTemplate().getAIDataStatic().getAiType(); } public void setDisplayEffect(int val) { if (val != _displayEffect) { _displayEffect = val; broadcastPacket(new ExChangeNpcState(getObjectId(), val)); } } public int getDisplayEffect() { return _displayEffect; } public int getColorEffect() { return 0; } }