/*
* 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.tables;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
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.commons.database.DatabaseFactory;
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.base.ClassId;
import silentium.gameserver.model.quest.Quest;
import silentium.gameserver.model.quest.Quest.QuestEventType;
import silentium.gameserver.skills.Formulas;
import silentium.gameserver.templates.StatsSet;
import silentium.gameserver.templates.chars.L2NpcTemplate;
public class NpcTable
{
private static Logger _log = LoggerFactory.getLogger(NpcTable.class.getName());
private final TIntObjectHashMap<L2NpcTemplate> _npcs = new TIntObjectHashMap<>();
public static NpcTable getInstance()
{
return SingletonHolder._instance;
}
protected NpcTable()
{
load();
}
public void reloadAllNpc()
{
_npcs.clear();
load();
}
private void load()
{
loadNpcs(0);
loadNpcsSkills(0);
loadNpcsDrop(0);
loadNpcsSkillLearn(0);
loadMinions(0);
loadNpcsAI(0);
}
/**
* Id equals to zero or lesser means all.
*
* @param id
* of the NPC to load.
*/
public void loadNpcs(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = null;
if (id > 0)
{
statement = con.prepareStatement("SELECT * FROM npc WHERE id = ?");
statement.setInt(1, id);
}
else
statement = con.prepareStatement("SELECT * FROM npc ORDER BY id");
ResultSet rset = statement.executeQuery();
while (rset.next())
{
StatsSet npcDat = new StatsSet();
int npcId = rset.getInt("id");
assert npcId < 1000000;
npcDat.set("npcId", npcId);
npcDat.set("idTemplate", rset.getInt("idTemplate"));
int level = rset.getInt("level");
npcDat.set("level", level);
npcDat.set("jClass", rset.getString("class"));
npcDat.set("baseShldDef", 0);
npcDat.set("baseShldRate", 0);
npcDat.set("baseCritRate", 38);
npcDat.set("name", rset.getString("name"));
npcDat.set("serverSideName", rset.getBoolean("serverSideName"));
npcDat.set("title", rset.getString("title"));
npcDat.set("serverSideTitle", rset.getBoolean("serverSideTitle"));
npcDat.set("collision_radius", rset.getDouble("collision_radius"));
npcDat.set("collision_height", rset.getDouble("collision_height"));
npcDat.set("sex", rset.getString("sex"));
npcDat.set("type", rset.getString("type"));
npcDat.set("baseAtkRange", rset.getInt("attackrange"));
npcDat.set("rewardExp", rset.getInt("exp"));
npcDat.set("rewardSp", rset.getInt("sp"));
npcDat.set("rhand", rset.getInt("rhand"));
npcDat.set("lhand", rset.getInt("lhand"));
npcDat.set("baseWalkSpd", rset.getInt("walkspd"));
npcDat.set("enchant", rset.getInt("enchant"));
npcDat.set("baseSTR", npcDat.getInteger("str", Formulas.BASENPCSTR));
npcDat.set("baseCON", npcDat.getInteger("con", Formulas.BASENPCCON));
npcDat.set("baseDEX", npcDat.getInteger("dex", Formulas.BASENPCDEX));
npcDat.set("baseINT", npcDat.getInteger("int", Formulas.BASENPCINT));
npcDat.set("baseWIT", npcDat.getInteger("wit", Formulas.BASENPCWIT));
npcDat.set("baseMEN", npcDat.getInteger("men", Formulas.BASENPCMEN));
// Calculating stats by using BaseStats (STR, DEX, CON, MEN, WIT, INT) FIXME: NPC stats
/*
* if (rset.getString("type").equalsIgnoreCase("L2Pet")) {
*/
npcDat.set("baseRunSpd", rset.getInt("runspd"));
npcDat.set("basePAtkSpd", rset.getInt("atkspd"));
npcDat.set("baseMAtkSpd", rset.getInt("matkspd"));
npcDat.set("basePAtk", rset.getInt("patk"));
npcDat.set("baseMAtk", rset.getInt("matk"));
npcDat.set("baseMDef", rset.getInt("mdef"));
npcDat.set("basePDef", rset.getInt("pdef"));
/*
* } else { npcDat.set("baseRunSpd", Formulas.calcNpcMoveBonus(DEX, rset.getInt("runspd"))); npcDat.set("basePAtkSpd",
* Formulas.calcNpcPatkSpdBonus(DEX, rset.getInt("atkspd"))); npcDat.set("baseMAtkSpd", Formulas.calcNpcMatkSpdBonus(WIT,
* rset.getInt("matkspd"))); npcDat.set("basePAtk", Formulas.calcNpcPatkBonus(STR, rset.getInt("patk"), level));
* npcDat.set("baseMAtk", Formulas.calcNpcMatkBonus(INT, rset.getInt("matk"), level)); npcDat.set("baseMDef",
* Formulas.calcNpcMdefBonus(MEN, rset.getInt("mdef"), level)); npcDat.set("basePDef",
* Formulas.calcNpcPdefBonus(rset.getInt("pdef"), level)); }
*/
npcDat.set("corpseDecayTime", rset.getInt("corpseDecayTime"));
npcDat.set("dropHerbGroup", rset.getInt("dropHerbGroup"));
// FIXME NPC stats
// npcDat.set("baseHpMax", Formulas.calcNpcHpBonus(CON, rset.getInt("hp")));
// npcDat.set("baseMpMax", Formulas.calcNpcMpBonus(MEN, rset.getInt("mp")));
npcDat.set("baseHpMax", rset.getInt("hp"));
npcDat.set("baseMpMax", rset.getInt("mp"));
npcDat.set("baseCpMax", 0);
npcDat.set("baseHpReg", rset.getFloat("hpreg") > 0 ? rset.getFloat("hpreg") : 1.5 + ((level - 1) / 10.0));
npcDat.set("baseMpReg", rset.getFloat("mpreg") > 0 ? rset.getFloat("mpreg") : 0.9 + 0.3 * ((level - 1) / 10.0));
_npcs.put(npcId, new L2NpcTemplate(npcDat));
}
rset.close();
statement.close();
_log.info("NpcTable: Loaded " + _npcs.size() + " NPC templates.");
}
catch (Exception e)
{
_log.error("NPCTable: Error creating NPC table.", e);
}
}
/**
* Id equals to zero or lesser means all.
*
* @param id
* of the NPC to load it's skills.
*/
public void loadNpcsSkills(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = null;
if (id > 0)
{
statement = con.prepareStatement("SELECT * FROM npc_skills WHERE npcid = ?");
statement.setInt(1, id);
}
else
statement = con.prepareStatement("SELECT * FROM npc_skills ORDER BY npcid");
ResultSet rset = statement.executeQuery();
L2NpcTemplate npcDat = null;
L2Skill npcSkill = null;
int cnt = 0;
while (rset.next())
{
int mobId = rset.getInt("npcid");
npcDat = _npcs.get(mobId);
if (npcDat == null)
{
_log.warn("NPCTable: Skill data for undefined NPC. npcId: " + mobId);
continue;
}
int skillId = rset.getInt("skillid");
int level = rset.getInt("level");
// Set up the npc's race.
if (skillId == L2Skill.SKILL_NPC_RACE)
{
npcDat.setRace(level);
continue;
}
npcSkill = SkillTable.getInstance().getInfo(skillId, level);
if (npcSkill == null)
continue;
cnt++;
npcDat.addSkill(npcSkill);
}
rset.close();
statement.close();
_log.info("NpcTable: Loaded " + cnt + " npc skills.");
}
catch (Exception e)
{
_log.error("NPCTable: Error reading NPC skills table.", e);
}
}
/**
* Id equals to zero or lesser means all.
*
* @param id
* of the NPC to load it's drops.
*/
public void loadNpcsDrop(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = null;
if (id > 0)
{
statement = con.prepareStatement("SELECT * FROM droplist WHERE mobId = ? ORDER BY mobId, chance DESC");
statement.setInt(1, id);
}
else
statement = con.prepareStatement("SELECT * FROM droplist ORDER BY mobId, chance DESC");
ResultSet rset = statement.executeQuery();
L2DropData dropDat = null;
L2NpcTemplate npcDat = null;
int cnt = 0;
while (rset.next())
{
int mobId = rset.getInt("mobId");
npcDat = _npcs.get(mobId);
if (npcDat == null)
{
_log.warn("NPCTable: Drop data for undefined NPC. npcId: " + mobId);
continue;
}
dropDat = new L2DropData();
dropDat.setItemId(rset.getInt("itemId"));
dropDat.setMinDrop(rset.getInt("min"));
dropDat.setMaxDrop(rset.getInt("max"));
dropDat.setChance(rset.getInt("chance"));
if (ItemTable.getInstance().getTemplate(dropDat.getItemId()) == null)
{
_log.warn("Drop data for undefined item template! NpcId: " + mobId + " itemId: " + dropDat.getItemId());
continue;
}
cnt++;
npcDat.addDropData(dropDat, rset.getInt("category"));
}
rset.close();
statement.close();
_log.info("NpcTable: Loaded " + cnt + " drops.");
}
catch (Exception e)
{
_log.error("NPCTable: Error reading NPC dropdata.", e);
}
}
/**
* Id equals to zero or lesser means all.
*
* @param id
* of the NPC to load it's skill learn list.
*/
private void loadNpcsSkillLearn(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = null;
if (id > 0)
{
statement = con.prepareStatement("SELECT * FROM skill_learn WHERE npc_id = ?");
statement.setInt(1, id);
}
else
statement = con.prepareStatement("SELECT * FROM skill_learn");
ResultSet rset = statement.executeQuery();
int cnt = 0;
while (rset.next())
{
int npcId = rset.getInt("npc_id");
int classId = rset.getInt("class_id");
L2NpcTemplate npc = getTemplate(npcId);
if (npc == null)
{
_log.warn("NPCTable: Error getting NPC template ID " + npcId + " while trying to load skill trainer data.");
continue;
}
cnt++;
npc.addTeachInfo(ClassId.values()[classId]);
}
rset.close();
statement.close();
_log.info("NpcTable: Loaded " + cnt + " Skill Learn.");
}
catch (Exception e)
{
_log.error("NPCTable: Error reading NPC trainer data.", e);
}
}
/**
* Id equals to zero or lesser means all.
*
* @param id
* of the NPC to load it's minions.
*/
public void loadMinions(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = null;
if (id > 0)
{
statement = con.prepareStatement("SELECT * FROM minions WHERE boss_id = ?");
statement.setInt(1, id);
}
else
statement = con.prepareStatement("SELECT * FROM minions ORDER BY boss_id");
ResultSet rset = statement.executeQuery();
L2MinionData minionDat = null;
L2NpcTemplate npcDat = null;
int cnt = 0;
while (rset.next())
{
int raidId = rset.getInt("boss_id");
npcDat = _npcs.get(raidId);
if (npcDat == null)
{
_log.warn("Minion references undefined boss NPC. Boss NpcId: " + raidId);
continue;
}
minionDat = new L2MinionData();
minionDat.setMinionId(rset.getInt("minion_id"));
minionDat.setAmountMin(rset.getInt("amount_min"));
minionDat.setAmountMax(rset.getInt("amount_max"));
cnt++;
npcDat.addRaidData(minionDat);
}
rset.close();
statement.close();
_log.info("NpcTable: Loaded " + cnt + " minions.");
}
catch (Exception e)
{
_log.error("NPCTable: Error loading minion data.", e);
}
}
/**
* Id equals to zero or lesser means all.
*
* @param id
* of the NPC to load it's AI data.
*/
public void loadNpcsAI(int id)
{
try (Connection con = DatabaseFactory.getConnection())
{
PreparedStatement statement = null;
if (id > 0)
{
statement = con.prepareStatement("SELECT * FROM npc_ai_data WHERE npc_id = ?");
statement.setInt(1, id);
}
else
statement = con.prepareStatement("SELECT * FROM npc_ai_data ORDER BY npc_id");
ResultSet rset = statement.executeQuery();
L2NpcAIData npcAIDat = null;
L2NpcTemplate npcDat = null;
int cnt = 0;
while (rset.next())
{
int npcId = rset.getInt("npc_id");
npcDat = _npcs.get(npcId);
if (npcDat == null)
{
_log.error("NPCTable: AI Data Error with id : " + npcId);
continue;
}
npcAIDat = new L2NpcAIData();
npcAIDat.setMinSkillChance(rset.getInt("min_skill_chance"));
npcAIDat.setMaxSkillChance(rset.getInt("max_skill_chance"));
npcAIDat.setPrimaryAttack(rset.getInt("primary_attack"));
npcAIDat.setCanMove(rset.getInt("can_move"));
npcAIDat.setShortRangeSkill(rset.getInt("minrangeskill"));
npcAIDat.setShortRangeChance(rset.getInt("minrangechance"));
npcAIDat.setLongRangeSkill(rset.getInt("maxrangeskill"));
npcAIDat.setLongRangeChance(rset.getInt("maxrangechance"));
npcAIDat.setSoulShot(rset.getInt("soulshot"));
npcAIDat.setSpiritShot(rset.getInt("spiritshot"));
npcAIDat.setSpiritShotChance(rset.getInt("spschance"));
npcAIDat.setSoulShotChance(rset.getInt("sschance"));
npcAIDat.setIsChaos(rset.getInt("is_chaos"));
npcAIDat.setAggro(rset.getInt("aggro"));
npcAIDat.setClan(rset.getString("clan"));
npcAIDat.setClanRange(rset.getInt("clan_range"));
npcAIDat.setEnemyClan(rset.getString("enemy_clan"));
npcAIDat.setEnemyRange(rset.getInt("enemy_range"));
npcAIDat.setAi(rset.getString("ai_type"));
npcDat.setAIData(npcAIDat);
cnt++;
}
rset.close();
statement.close();
_log.info("NpcTable: Loaded " + cnt + " AIs.");
}
catch (Exception e)
{
_log.error("NPCTable: Error reading NPC AI Data: " + e.getMessage(), e);
}
}
public void reloadNpc(int id)
{
try
{
// save a copy of the old data
L2NpcTemplate old = getTemplate(id);
TIntObjectHashMap<L2Skill> skills = new TIntObjectHashMap<>();
List<L2MinionData> minions = new FastList<>();
Map<QuestEventType, Quest[]> quests = new FastMap<>();
List<ClassId> classIds = new FastList<>();
FastList<L2DropCategory> categories = new FastList<>();
if (old != null)
{
skills.putAll(old.getSkills());
categories.addAll(old.getDropData());
classIds.addAll(old.getTeachInfo());
minions.addAll(old.getMinionData());
if (!old.getEventQuests().isEmpty())
quests.putAll(old.getEventQuests());
}
loadNpcs(id);
loadNpcsSkills(id);
loadNpcsDrop(id);
loadNpcsSkillLearn(id);
loadMinions(id);
loadNpcsAI(id);
// restore additional data from saved copy
L2NpcTemplate created = getTemplate(id);
if ((old != null) && (created != null))
{
if (!skills.isEmpty())
{
for (L2Skill skill : skills.values(new L2Skill[0]))
created.addSkill(skill);
}
for (ClassId classId : classIds)
created.addTeachInfo(classId);
if (!minions.isEmpty())
{
for (L2MinionData minion : minions)
created.addRaidData(minion);
}
if (!quests.isEmpty())
created.getEventQuests().putAll(quests);
}
}
catch (Exception e)
{
_log.warn("NPCTable: Could not reload data for NPC " + id + ": " + e.getMessage(), e);
}
}
public void saveNpc(StatsSet npc)
{
Map<String, Object> set = npc.getSet();
int length = 0;
for (Object obj : set.keySet())
{
// 15 is just guessed npc name length
length += ((String) obj).length() + 7 + 15;
}
final StringBuilder sbValues = new StringBuilder(length);
for (Object obj : set.keySet())
{
final String name = (String) obj;
if (!name.equalsIgnoreCase("npcId"))
{
if (sbValues.length() > 0)
{
sbValues.append(", ");
}
sbValues.append(name);
sbValues.append(" = '");
sbValues.append(set.get(name));
sbValues.append('\'');
}
}
try (Connection con = DatabaseFactory.getConnection())
{
final StringBuilder sbQuery = new StringBuilder(sbValues.length() + 28);
sbQuery.append("UPDATE npc SET ");
sbQuery.append(sbValues.toString());
sbQuery.append(" WHERE id = ?");
PreparedStatement statement = con.prepareStatement(sbQuery.toString());
statement.setInt(1, npc.getInteger("npcId"));
statement.executeUpdate();
statement.close();
}
catch (Exception e)
{
_log.warn("NPCTable: Could not store new NPC data in database: " + e.getMessage(), e);
}
}
public L2NpcTemplate getTemplate(int id)
{
return _npcs.get(id);
}
public L2NpcTemplate getTemplateByName(String name)
{
for (L2NpcTemplate npcTemplate : _npcs.values(new L2NpcTemplate[0]))
if (npcTemplate.getName().equalsIgnoreCase(name))
return npcTemplate;
return null;
}
public List<L2NpcTemplate> getAllOfLevel(int... lvls)
{
final List<L2NpcTemplate> list = new FastList<>();
for (int lvl : lvls)
{
for (L2NpcTemplate t : _npcs.values(new L2NpcTemplate[0]))
{
if (t.getLevel() == lvl)
list.add(t);
}
}
return list;
}
public List<L2NpcTemplate> getAllMonstersOfLevel(int... lvls)
{
final List<L2NpcTemplate> list = new FastList<>();
for (int lvl : lvls)
{
for (L2NpcTemplate t : _npcs.values(new L2NpcTemplate[0]))
{
if ((t.getLevel() == lvl) && t.isType("L2Monster"))
list.add(t);
}
}
return list;
}
/**
* @param letters
* of all the NPC templates which its name start with.
* @return the template list for the given letter.
*/
public List<L2NpcTemplate> getAllNpcStartingWith(String... letters)
{
final List<L2NpcTemplate> list = new FastList<>();
for (String letter : letters)
{
for (L2NpcTemplate t : _npcs.values(new L2NpcTemplate[0]))
{
if (t.getName().startsWith(letter) && t.isType("L2Npc"))
list.add(t);
}
}
return list;
}
public List<L2NpcTemplate> getAllNpcOfClassType(String... classTypes)
{
final List<L2NpcTemplate> list = new FastList<>();
for (String classType : classTypes)
{
for (L2NpcTemplate t : _npcs.values(new L2NpcTemplate[0]))
{
if (t.isType(classType))
list.add(t);
}
}
return list;
}
private static class SingletonHolder
{
protected static final NpcTable _instance = new NpcTable();
}
}