/* * 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.templates.chars; import gnu.trove.map.hash.TIntObjectHashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javolution.util.FastList; import javolution.util.FastMap; import com.l2jserver.gameserver.datatables.HerbDropTable; import com.l2jserver.gameserver.model.L2DropCategory; import com.l2jserver.gameserver.model.L2DropData; import com.l2jserver.gameserver.model.L2MinionData; import com.l2jserver.gameserver.model.L2NpcAIData; import com.l2jserver.gameserver.model.L2Skill; import com.l2jserver.gameserver.model.actor.instance.L2XmassTreeInstance; import com.l2jserver.gameserver.model.base.ClassId; import com.l2jserver.gameserver.model.quest.Quest; import com.l2jserver.gameserver.model.quest.Quest.QuestEventType; import com.l2jserver.gameserver.templates.StatsSet; /** * @author Zoey76 */ public final class L2NpcTemplate extends L2CharTemplate { private static final Logger _log = Logger.getLogger(L2NpcTemplate.class.getName()); private final int _npcId; private final int _idTemplate; private final String _type; private final String _name; private final boolean _serverSideName; private final String _title; private final boolean _serverSideTitle; private final String _sex; private final byte _level; private final int _rewardExp; private final int _rewardSp; private final int _aggroRange; private final int _rHand; private final int _lHand; private final int _enchantEffect; private Race _race; private final String _clientClass; private final int _dropHerbGroup; private final boolean _isCustom; /** * Doesn't include all mobs that are involved in quests, just plain quest monsters for preventing champion spawn. */ private final boolean _isQuestMonster; private final float _baseVitalityDivider; // Skill AI private final FastList _buffSkills = new FastList<>(); private final FastList _negativeSkills = new FastList<>(); private final FastList _debuffSkills = new FastList<>(); private final FastList _atkSkills = new FastList<>(); private final FastList _rootSkills = new FastList<>(); private final FastList _stunskills = new FastList<>(); private final FastList _sleepSkills = new FastList<>(); private final FastList _paralyzeSkills = new FastList<>(); private final FastList _fossilSkills = new FastList<>(); private final FastList _floatSkills = new FastList<>(); private final FastList _immobilizeSkills = new FastList<>(); private final FastList _healSkills = new FastList<>(); private final FastList _resSkills = new FastList<>(); private final FastList _dotSkills = new FastList<>(); private final FastList _cotSkills = new FastList<>(); private final FastList _universalSkills = new FastList<>(); private final FastList _manaSkills = new FastList<>(); private final FastList _longRangeSkills = new FastList<>(); private final FastList _shortRangeSkills = new FastList<>(); private final FastList _generalSkills = new FastList<>(); private final FastList _suicideSkills = new FastList<>(); private L2NpcAIData _AIdataStatic = new L2NpcAIData(); /** * The table containing all Item that can be dropped by L2NpcInstance using this L2NpcTemplate */ private final FastList _categories = new FastList<>(); /** * The table containing all Minions that must be spawn with the L2NpcInstance using this L2NpcTemplate */ private final List _minions = new FastList<>(); private final List _teachInfo = new FastList<>(); private final TIntObjectHashMap _skills = new TIntObjectHashMap<>(); /** * Contains a list of quests for each event type (questStart, questAttack, questKill, etc). */ private final Map _questEvents = new FastMap<>(); public static enum AIType { FIGHTER, ARCHER, BALANCED, MAGE, HEALER, CORPSE } public static enum Race { UNDEAD, MAGICCREATURE, BEAST, ANIMAL, PLANT, HUMANOID, SPIRIT, ANGEL, DEMON, DRAGON, GIANT, BUG, FAIRIE, HUMAN, ELVE, DARKELVE, ORC, DWARVE, OTHER, NONLIVING, SIEGEWEAPON, DEFENDINGARMY, MERCENARIE, UNKNOWN, KAMAEL, NONE } public static boolean isAssignableTo(Class sub, Class clazz) { // If clazz represents an interface if (clazz.isInterface()) { // check if obj implements the clazz interface Class[] interfaces = sub.getInterfaces(); for (Class interface1 : interfaces) { if (clazz.getName().equals(interface1.getName())) { return true; } } } else { do { if (sub.getName().equals(clazz.getName())) { return true; } sub = sub.getSuperclass(); } while (sub != null); } return false; } /** * Checks if obj can be assigned to the Class represented by clazz.
* This is true if, and only if, obj is the same class represented by clazz, or a subclass of it or obj implements the interface represented by clazz. * @param obj * @param clazz * @return */ public static boolean isAssignableTo(Object obj, Class clazz) { return L2NpcTemplate.isAssignableTo(obj.getClass(), clazz); } /** * Constructor of L2Character. * @param set The StatsSet object to transfer data to the method */ public L2NpcTemplate(StatsSet set) { super(set); _npcId = set.getInteger("npcId"); _idTemplate = set.getInteger("idTemplate"); _type = set.getString("type"); _name = set.getString("name"); _serverSideName = set.getBool("serverSideName"); _title = set.getString("title"); _isQuestMonster = getTitle().equalsIgnoreCase("Quest Monster"); _serverSideTitle = set.getBool("serverSideTitle"); _sex = set.getString("sex"); _level = set.getByte("level"); _rewardExp = set.getInteger("rewardExp"); _rewardSp = set.getInteger("rewardSp"); _aggroRange = set.getInteger("aggroRange"); _rHand = set.getInteger("rhand"); _lHand = set.getInteger("lhand"); _enchantEffect = set.getInteger("enchant"); _race = null; final int herbGroup = set.getInteger("dropHerbGroup"); if ((herbGroup > 0) && (HerbDropTable.getInstance().getHerbDroplist(herbGroup) == null)) { _log.warning("Missing Herb Drop Group for npcId: " + getNpcId()); _dropHerbGroup = 0; } else { _dropHerbGroup = herbGroup; } _clientClass = set.getString("client_class"); // TODO: Could be loaded from db. _baseVitalityDivider = (getLevel() > 0) && (getRewardExp() > 0) ? (getBaseHpMax() * 9 * getLevel() * getLevel()) / (100 * getRewardExp()) : 0; _isCustom = _npcId != _idTemplate; } public void addAtkSkill(L2Skill skill) { _atkSkills.add(skill); } public void addBuffSkill(L2Skill skill) { _buffSkills.add(skill); } public void addCOTSkill(L2Skill skill) { _cotSkills.add(skill); } public void addDebuffSkill(L2Skill skill) { _debuffSkills.add(skill); } public void addDOTSkill(L2Skill skill) { _dotSkills.add(skill); } /** * Add a drop to a given category.
* If the category does not exist, create it. * @param drop * @param categoryType */ public void addDropData(L2DropData drop, int categoryType) { if (drop.isQuestDrop()) { // if (_questDrops == null) // _questDrops = new FastList(0); // _questDrops.add(drop); } else { // If the category doesn't already exist, create it first synchronized (_categories) { boolean catExists = false; for (L2DropCategory cat : _categories) { // If the category exists, add the drop to this category. if (cat.getCategoryType() == categoryType) { cat.addDropData(drop, isType("L2RaidBoss") || isType("L2GrandBoss")); catExists = true; break; } } // If the category doesn't exit, create it and add the drop if (!catExists) { final L2DropCategory cat = new L2DropCategory(categoryType); cat.addDropData(drop, isType("L2RaidBoss") || isType("L2GrandBoss")); _categories.add(cat); } } } } public void addFloatSkill(L2Skill skill) { _floatSkills.add(skill); } public void addFossilSkill(L2Skill skill) { _fossilSkills.add(skill); } public void addGeneralSkill(L2Skill skill) { getGeneralskills().add(skill); } public void addHealSkill(L2Skill skill) { _healSkills.add(skill); } public void addImmobiliseSkill(L2Skill skill) { _immobilizeSkills.add(skill); } public void addManaHealSkill(L2Skill skill) { _manaSkills.add(skill); } public void addNegativeSkill(L2Skill skill) { _negativeSkills.add(skill); } public void addParalyzeSkill(L2Skill skill) { _paralyzeSkills.add(skill); } public void addQuestEvent(Quest.QuestEventType EventType, Quest q) { if (_questEvents.get(EventType) == null) { _questEvents.put(EventType, new Quest[] { q }); } else { Quest[] _quests = _questEvents.get(EventType); int len = _quests.length; // if only one registration per npc is allowed for this event type // then only register this NPC if not already registered for the specified event. // if a quest allows multiple registrations, then register regardless of count // In all cases, check if this new registration is replacing an older copy of the SAME quest // Finally, check quest class hierarchy: a parent class should never replace a child class. // a child class should always replace a parent class. if (!EventType.isMultipleRegistrationAllowed()) { // if it is the same quest (i.e. reload) or the existing is a superclass of the new one, replace the existing. if (_quests[0].getName().equals(q.getName()) || L2NpcTemplate.isAssignableTo(q, _quests[0].getClass())) { _quests[0] = q; } else { _log.warning("Quest event not allowed in multiple quests. Skipped addition of Event Type \"" + EventType + "\" for NPC \"" + _name + "\" and quest \"" + q.getName() + "\"."); } } else { // be ready to add a new quest to a new copy of the list, with larger size than previously. Quest[] tmp = new Quest[len + 1]; // loop through the existing quests and copy them to the new list. While doing so, also // check if this new quest happens to be just a replacement for a previously loaded quest. // Replace existing if the new quest is the same (reload) or a child of the existing quest. // Do nothing if the new quest is a superclass of an existing quest. // Add the new quest in the end of the list otherwise. for (int i = 0; i < len; i++) { if (_quests[i].getName().equals(q.getName()) || L2NpcTemplate.isAssignableTo(q, _quests[i].getClass())) { _quests[i] = q; return; } else if (L2NpcTemplate.isAssignableTo(_quests[i], q.getClass())) { return; } tmp[i] = _quests[i]; } tmp[len] = q; _questEvents.put(EventType, tmp); } } } public void addRaidData(L2MinionData minion) { _minions.add(minion); } public void addRangeSkill(L2Skill skill) { if ((skill.getCastRange() <= 150) && (skill.getCastRange() > 0)) { _shortRangeSkills.add(skill); } else if (skill.getCastRange() > 150) { _longRangeSkills.add(skill); } } public void addResSkill(L2Skill skill) { _resSkills.add(skill); } public void addRootSkill(L2Skill skill) { _rootSkills.add(skill); } public void addSkill(L2Skill skill) { if (!skill.isPassive()) { if (skill.isSuicideAttack()) { addSuicideSkill(skill); } else { addGeneralSkill(skill); switch (skill.getSkillType()) { case BUFF: addBuffSkill(skill); break; case HEAL: case HOT: case HEAL_PERCENT: case HEAL_STATIC: case BALANCE_LIFE: addHealSkill(skill); break; case RESURRECT: addResSkill(skill); break; case DEBUFF: addDebuffSkill(skill); addCOTSkill(skill); addRangeSkill(skill); break; case ROOT: addRootSkill(skill); addImmobiliseSkill(skill); addRangeSkill(skill); break; case SLEEP: addSleepSkill(skill); addImmobiliseSkill(skill); break; case STUN: addRootSkill(skill); addImmobiliseSkill(skill); addRangeSkill(skill); break; case PARALYZE: addParalyzeSkill(skill); addImmobiliseSkill(skill); addRangeSkill(skill); break; case PDAM: case MDAM: case BLOW: case DRAIN: case CHARGEDAM: case FATAL: case DEATHLINK: case CPDAM: case MANADAM: case CPDAMPERCENT: addAtkSkill(skill); addUniversalSkill(skill); addRangeSkill(skill); break; case POISON: case DOT: case MDOT: case BLEED: addDOTSkill(skill); addRangeSkill(skill); break; case MUTE: case FEAR: addCOTSkill(skill); addRangeSkill(skill); break; case CANCEL: case NEGATE: addNegativeSkill(skill); addRangeSkill(skill); break; default: addUniversalSkill(skill); break; } } } _skills.put(skill.getId(), skill); } public void addSleepSkill(L2Skill skill) { _sleepSkills.add(skill); } public void addStunSkill(L2Skill skill) { _stunskills.add(skill); } public void addSuicideSkill(L2Skill skill) { _suicideSkills.add(skill); } public void addTeachInfo(ClassId classId) { _teachInfo.add(classId); } public void addUniversalSkill(L2Skill skill) { _universalSkills.add(skill); } public boolean canTeach(ClassId classId) { // If the player is on a third class, fetch the class teacher // information for its parent class. if (classId.level() == 3) { return _teachInfo.contains(classId.getParent()); } return _teachInfo.contains(classId); } /** * Empty all possible drops of this L2NpcTemplate. */ public synchronized void clearAllDropData() { while (!_categories.isEmpty()) { _categories.getFirst().clearAllDrops(); _categories.removeFirst(); } _categories.clear(); } /** * @return the aggro range value. */ public int getAggroRange() { return _aggroRange; } public L2NpcAIData getAIDataStatic() { return _AIdataStatic; } /** * @return the list of all possible item drops of this L2NpcTemplate.
* (ie full drops and part drops, mats, miscellaneous & UNCATEGORIZED) */ public List getAllDropData() { final List list = new FastList<>(); for (L2DropCategory tmp : _categories) { list.addAll(tmp.getAllDrops()); } return list; } /** * @return the attack skills. */ public FastList getAtkSkills() { return _atkSkills; } /** * @return the base vitality divider value. */ public float getBaseVitalityDivider() { return _baseVitalityDivider; } /** * @return the buff skills. */ public FastList getBuffSkills() { return _buffSkills; } /** * @return the client class (same as texture path). */ public String getClientClass() { return _clientClass; } /** * @return the cost over time skills. */ public FastList getCostOverTimeSkills() { return _cotSkills; } /** * @return the debuff skills. */ public FastList getDebuffSkills() { return _debuffSkills; } /** * @return the list of all possible UNCATEGORIZED drops of this L2NpcTemplate. */ public FastList getDropData() { return _categories; } /** * @return the drop herb group. */ public int getDropHerbGroup() { return _dropHerbGroup; } /** * @return the enchant effect. */ public int getEnchantEffect() { return _enchantEffect; } public Map getEventQuests() { return _questEvents; } public Quest[] getEventQuests(QuestEventType EventType) { return _questEvents.get(EventType); } /** * @return the general skills. */ public FastList getGeneralskills() { return _generalSkills; } /** * @return the heal skills. */ public FastList getHealSkills() { return _healSkills; } /** * @return the Id template. */ public int getIdTemplate() { return _idTemplate; } /** * @return the immobilize skills. */ public FastList getImmobiliseSkills() { return _immobilizeSkills; } /** * @return the left hand item. */ public int getLeftHand() { return _lHand; } /** * @return the NPC level. */ public byte getLevel() { return _level; } /** * @return the long range skills. */ public FastList getLongRangeSkills() { return _longRangeSkills; } /** * @return the list of all Minions that must be spawn with the L2NpcInstance using this L2NpcTemplate. */ public List getMinionData() { return _minions; } /** * @return the NPC name. */ public String getName() { return _name; } /** * @return the negative skills. */ public FastList getNegativeSkills() { return _negativeSkills; } /** * @return the npc Id. */ public int getNpcId() { return _npcId; } /** * @return the NPC race. */ public L2NpcTemplate.Race getRace() { if (_race == null) { _race = L2NpcTemplate.Race.NONE; } return _race; } /** * @return the resurrection skills. */ public FastList getResSkills() { return _resSkills; } /** * @return the reward Exp. */ public int getRewardExp() { return _rewardExp; } /** * @return the reward SP. */ public int getRewardSp() { return _rewardSp; } /** * @return the right hand weapon. */ public int getRightHand() { return _rHand; } /** * @return the NPC sex. */ public String getSex() { return _sex; } /** * @return the short range skills. */ public FastList getShortRangeSkills() { return _shortRangeSkills; } public TIntObjectHashMap getSkills() { return _skills; } public L2Skill[] getSkillsArray() { return _skills.values(new L2Skill[0]); } public FastList getSuicideSkills() { return _suicideSkills; } public List getTeachInfo() { return _teachInfo; } /** * @return the NPC title. */ public String getTitle() { return _title; } /** * @return the NPC type. */ public String getType() { return _type; } /** * @return the universal skills. */ public FastList getUniversalSkills() { return _universalSkills; } /** * @return {@code true} if the NPC is custom, {@code false} otherwise. */ public boolean isCustom() { return _isCustom; } /** * @return {@code true} if the NPC is a quest monster, {@code false} otherwise. */ public boolean isQuestMonster() { return _isQuestMonster; } /** * @return {@code true} if the NPC uses server side name, {@code false} otherwise. */ public boolean isServerSideName() { return _serverSideName; } /** * @return {@code true} if the NPC uses server side title, {@code false} otherwise. */ public boolean isServerSideTitle() { return _serverSideTitle; } /** * @return {@code true} if the NPC is Christmas Special Tree, {@code false} otherwise. */ public boolean isSpecialTree() { return _npcId == L2XmassTreeInstance.SPECIAL_TREE_ID; } /** * Checks types, ignore case. * @param t the type to check. * @return {@code true} if the type are the same, {@code false} otherwise. */ public boolean isType(String t) { return _type.equalsIgnoreCase(t); } /** * @return {@code true} if the NPC is an undead, {@code false} otherwise. */ public boolean isUndead() { return _race == Race.UNDEAD; } public void setAIData(L2NpcAIData AIData) { _AIdataStatic = AIData; } public void setRace(int raceId) { switch (raceId) { case 1: _race = L2NpcTemplate.Race.UNDEAD; break; case 2: _race = L2NpcTemplate.Race.MAGICCREATURE; break; case 3: _race = L2NpcTemplate.Race.BEAST; break; case 4: _race = L2NpcTemplate.Race.ANIMAL; break; case 5: _race = L2NpcTemplate.Race.PLANT; break; case 6: _race = L2NpcTemplate.Race.HUMANOID; break; case 7: _race = L2NpcTemplate.Race.SPIRIT; break; case 8: _race = L2NpcTemplate.Race.ANGEL; break; case 9: _race = L2NpcTemplate.Race.DEMON; break; case 10: _race = L2NpcTemplate.Race.DRAGON; break; case 11: _race = L2NpcTemplate.Race.GIANT; break; case 12: _race = L2NpcTemplate.Race.BUG; break; case 13: _race = L2NpcTemplate.Race.FAIRIE; break; case 14: _race = L2NpcTemplate.Race.HUMAN; break; case 15: _race = L2NpcTemplate.Race.ELVE; break; case 16: _race = L2NpcTemplate.Race.DARKELVE; break; case 17: _race = L2NpcTemplate.Race.ORC; break; case 18: _race = L2NpcTemplate.Race.DWARVE; break; case 19: _race = L2NpcTemplate.Race.OTHER; break; case 20: _race = L2NpcTemplate.Race.NONLIVING; break; case 21: _race = L2NpcTemplate.Race.SIEGEWEAPON; break; case 22: _race = L2NpcTemplate.Race.DEFENDINGARMY; break; case 23: _race = L2NpcTemplate.Race.MERCENARIE; break; case 24: _race = L2NpcTemplate.Race.UNKNOWN; break; case 25: _race = L2NpcTemplate.Race.KAMAEL; break; default: _race = L2NpcTemplate.Race.NONE; break; } } }