/* * 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 <http://www.gnu.org/licenses/>. */ package silentium.gameserver.templates.chars; import gnu.trove.map.hash.TIntObjectHashMap; import java.util.List; import java.util.Map; import javolution.util.FastList; import javolution.util.FastMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import silentium.gameserver.data.xml.HerbDropData; import silentium.gameserver.model.L2DropCategory; import silentium.gameserver.model.L2DropData; import silentium.gameserver.model.L2MinionData; import silentium.gameserver.model.L2NpcAIData; import silentium.gameserver.model.L2Skill; import silentium.gameserver.model.actor.instance.L2XmassTreeInstance; import silentium.gameserver.model.base.ClassId; import silentium.gameserver.model.quest.Quest; import silentium.gameserver.model.quest.Quest.QuestEventType; import silentium.gameserver.templates.StatsSet; /** * This class contains all generic data of a L2Spawn object. */ public final class L2NpcTemplate extends L2CharTemplate { protected static final Logger _log = LoggerFactory.getLogger(Quest.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 _rHand; private final int _lHand; private final int _enchantEffect; private final int _corpseDecayTime; private int _dropHerbGroup; private Race _race; private final boolean _cantBeChampionMonster; // used for champion option ; avoid to popup champion quest mob. // Skills AI private final FastList<L2Skill> _buffSkills = new FastList<>(); private final FastList<L2Skill> _negativeSkills = new FastList<>(); private final FastList<L2Skill> _debuffSkills = new FastList<>(); private final FastList<L2Skill> _atkSkills = new FastList<>(); private final FastList<L2Skill> _rootSkills = new FastList<>(); private final FastList<L2Skill> _stunSkills = new FastList<>(); private final FastList<L2Skill> _sleepSkills = new FastList<>(); private final FastList<L2Skill> _paralyzeSkills = new FastList<>(); private final FastList<L2Skill> _fossilSkills = new FastList<>(); private final FastList<L2Skill> _immobilizeSkills = new FastList<>(); private final FastList<L2Skill> _healSkills = new FastList<>(); private final FastList<L2Skill> _dotSkills = new FastList<>(); private final FastList<L2Skill> _cotSkills = new FastList<>(); private final FastList<L2Skill> _universalSkills = new FastList<>(); private final FastList<L2Skill> _manaSkills = new FastList<>(); private final FastList<L2Skill> _longRangeSkills = new FastList<>(); private final FastList<L2Skill> _shortRangeSkills = new FastList<>(); private final FastList<L2Skill> _generalSkills = new FastList<>(); private final FastList<L2Skill> _suicideSkills = new FastList<>(); private L2NpcAIData _AIdataStatic = new L2NpcAIData(); 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 } private final FastList<L2DropCategory> _categories = new FastList<>(); private final List<L2MinionData> _minions = new FastList<>(); private final List<ClassId> _teachInfo = new FastList<>(); private final TIntObjectHashMap<L2Skill> _skills = new TIntObjectHashMap<>(); private final Map<QuestEventType, Quest[]> _questEvents = new FastMap<>(); /** * Constructor of L2Character.<BR> * <BR> * * @param set * The StatsSet object to transfert 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"); _cantBeChampionMonster = (_title.equalsIgnoreCase("Quest Monster") || isType("L2Chest")) ? true : false; _serverSideTitle = set.getBool("serverSideTitle"); _sex = set.getString("sex"); _level = set.getByte("level"); _rewardExp = set.getInteger("rewardExp"); _rewardSp = set.getInteger("rewardSp"); _rHand = set.getInteger("rhand"); _lHand = set.getInteger("lhand"); _enchantEffect = set.getInteger("enchant"); _race = null; _corpseDecayTime = set.getInteger("corpseDecayTime"); _dropHerbGroup = set.getInteger("dropHerbGroup"); if (_dropHerbGroup > 0 && HerbDropData.getInstance().getHerbDroplist(_dropHerbGroup) == null) { _log.warn("Missing dropHerbGroup information for npcId: " + _npcId + ", dropHerbGroup: " + _dropHerbGroup); _dropHerbGroup = 0; } } public void addTeachInfo(ClassId classId) { _teachInfo.add(classId); } public List<ClassId> getTeachInfo() { return _teachInfo; } 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); } // Add a drop to a given category. If the category does not exist, create it. public void addDropData(L2DropData drop, int categoryType) { if (!drop.isQuestDrop()) { // 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) { L2DropCategory cat = new L2DropCategory(categoryType); cat.addDropData(drop, isType("L2RaidBoss") || isType("L2GrandBoss")); _categories.add(cat); } } } } public void addRaidData(L2MinionData minion) { _minions.add(minion); } 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 DEBUFF: addDebuffSkill(skill); addCOTSkill(skill); addRangeSkill(skill); break; case ROOT: addRootSkill(skill); addImmobilizeSkill(skill); addRangeSkill(skill); break; case SLEEP: addSleepSkill(skill); addImmobilizeSkill(skill); break; case STUN: addRootSkill(skill); addImmobilizeSkill(skill); addRangeSkill(skill); break; case PARALYZE: addParalyzeSkill(skill); addImmobilizeSkill(skill); addRangeSkill(skill); break; case PDAM: case MDAM: case BLOW: case DRAIN: case CHARGEDAM: case FATAL: case DEATHLINK: 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); } /** * @return the list of all possible UNCATEGORIZED drops of this L2NpcTemplate. */ public FastList<L2DropCategory> getDropData() { return _categories; } /** * @return the list of all possible item drops of this L2NpcTemplate. (ie full drops and part drops, mats, miscellaneous & UNCATEGORIZED) */ public List<L2DropData> getAllDropData() { final List<L2DropData> list = new FastList<>(); for (L2DropCategory tmp : _categories) list.addAll(tmp.getAllDrops()); return list; } /** * Empty all possible drops of this L2NpcTemplate. */ public synchronized void clearAllDropData() { while (!_categories.isEmpty()) { _categories.getFirst().clearAllDrops(); _categories.removeFirst(); } _categories.clear(); } /** * @return the list of all Minions that must be spawn with the L2Npc using this L2NpcTemplate. */ public List<L2MinionData> getMinionData() { return _minions; } public TIntObjectHashMap<L2Skill> getSkills() { return _skills; } public L2Skill[] getSkillsArray() { return _skills.values(new L2Skill[0]); } 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 if (!EventType.isMultipleRegistrationAllowed()) { if (_quests[0].getName().equals(q.getName()) || L2NpcTemplate.isAssignableTo(q, _quests[0].getClass())) _quests[0] = q; else _log.warn("Quest event not allowed in multiple quests. Skipped addition of Event Type \"" + EventType + "\" for NPC \"" + getName() + "\" 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. // If so, just save the updated reference and do NOT use the new list. Else, add the new // quest to the end of the new list 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); } } } /** * Checks if obj can be assigned to the Class represented by clazz.<br> * 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); } 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; } public Map<QuestEventType, Quest[]> getEventQuests() { return _questEvents; } public Quest[] getEventQuests(QuestEventType EventType) { return _questEvents.get(EventType); } public void setRace(int raceId) { switch (raceId) { case 1: _race = Race.UNDEAD; break; case 2: _race = Race.MAGICCREATURE; break; case 3: _race = Race.BEAST; break; case 4: _race = Race.ANIMAL; break; case 5: _race = Race.PLANT; break; case 6: _race = Race.HUMANOID; break; case 7: _race = Race.SPIRIT; break; case 8: _race = Race.ANGEL; break; case 9: _race = Race.DEMON; break; case 10: _race = Race.DRAGON; break; case 11: _race = Race.GIANT; break; case 12: _race = Race.BUG; break; case 13: _race = Race.FAIRIE; break; case 14: _race = Race.HUMAN; break; case 15: _race = Race.ELVE; break; case 16: _race = Race.DARKELVE; break; case 17: _race = Race.ORC; break; case 18: _race = Race.DWARVE; break; case 19: _race = Race.OTHER; break; case 20: _race = Race.NONLIVING; break; case 21: _race = Race.SIEGEWEAPON; break; case 22: _race = Race.DEFENDINGARMY; break; case 23: _race = Race.MERCENARIE; break; default: _race = Race.UNKNOWN; break; } } // ----------------------------------------------------------------------- // Getters /** * @return the npc id. */ public int getNpcId() { return _npcId; } /** * @return the npc name. */ public String getName() { return _name; } /** * @return the npc name. */ public String getTitle() { return _title; } /** * @return the npc race. */ public Race getRace() { if (_race == null) _race = Race.UNKNOWN; return _race; } public String getType() { return _type; } /** * @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 right hand weapon. */ public int getLeftHand() { return _lHand; } /** * @return the NPC sex. */ public String getSex() { return _sex; } /** * @return the NPC level. */ public byte getLevel() { return _level; } /** * @return the drop herb group. */ public int getDropHerbGroup() { return _dropHerbGroup; } /** * @return the enchant effect. */ public int getEnchantEffect() { return _enchantEffect; } /** * @return the Id template. */ public int getIdTemplate() { return _idTemplate; } /** * @return true if the NPC uses server side name, false otherwise. */ public boolean isServerSideName() { return _serverSideName; } /** * @return true if the NPC uses server side title, false otherwise. */ public boolean isServerSideTitle() { return _serverSideTitle; } /** * @return the corpse decay time of the template. */ public int getCorpseDecayTime() { return _corpseDecayTime; } // ----------------------------------------------------------------------- // Npc AI Data By ShanSoft public void setAIData(L2NpcAIData aidata) { _AIdataStatic = aidata; } public L2NpcAIData getAIDataStatic() { return _AIdataStatic; } public void addBuffSkill(L2Skill skill) { _buffSkills.add(skill); } public void addHealSkill(L2Skill skill) { _healSkills.add(skill); } public void addAtkSkill(L2Skill skill) { _atkSkills.add(skill); } public void addDebuffSkill(L2Skill skill) { _debuffSkills.add(skill); } public void addRootSkill(L2Skill skill) { _rootSkills.add(skill); } public void addSleepSkill(L2Skill skill) { _sleepSkills.add(skill); } public void addStunSkill(L2Skill skill) { _stunSkills.add(skill); } public void addParalyzeSkill(L2Skill skill) { _paralyzeSkills.add(skill); } public void addFossilSkill(L2Skill skill) { _fossilSkills.add(skill); } public void addNegativeSkill(L2Skill skill) { _negativeSkills.add(skill); } public void addImmobilizeSkill(L2Skill skill) { _immobilizeSkills.add(skill); } public void addDOTSkill(L2Skill skill) { _dotSkills.add(skill); } public void addUniversalSkill(L2Skill skill) { _universalSkills.add(skill); } public void addCOTSkill(L2Skill skill) { _cotSkills.add(skill); } public void addManaHealSkill(L2Skill skill) { _manaSkills.add(skill); } public void addGeneralSkill(L2Skill skill) { _generalSkills.add(skill); } 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 addSuicideSkill(L2Skill skill) { _suicideSkills.add(skill); } public FastList<L2Skill> getUniversalSkills() { return _universalSkills; } public FastList<L2Skill> getSuicideSkills() { return _suicideSkills; } public FastList<L2Skill> getNegativeSkills() { return _negativeSkills; } public FastList<L2Skill> getImmobilizeSkills() { return _immobilizeSkills; } public FastList<L2Skill> getGeneralSkills() { return _generalSkills; } public FastList<L2Skill> getHealSkills() { return _healSkills; } public FastList<L2Skill> getCostOverTimeSkills() { return _cotSkills; } public FastList<L2Skill> getDebuffSkills() { return _debuffSkills; } public FastList<L2Skill> getBuffSkills() { return _buffSkills; } public FastList<L2Skill> getAtkSkills() { return _atkSkills; } /** * @return the long range skills. */ public FastList<L2Skill> getLongRangeSkills() { return _longRangeSkills; } /** * @return the short range skills. */ public FastList<L2Skill> getShortRangeSkills() { return _shortRangeSkills; } // ----------------------------------------------------------------------- // Misc public boolean isSpecialTree() { return _npcId == L2XmassTreeInstance.SPECIAL_TREE_ID; } public boolean isUndead() { return _race == Race.UNDEAD; } public boolean cantBeChampion() { return _cantBeChampionMonster; } /** * Checks types, ignore case. * * @param t * the type to check. * @return true if the type are the same, false otherwise. */ public boolean isType(String t) { return _type.equalsIgnoreCase(t); } }