/*
* 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;
}
}
}