/*
* 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.datatables;
import gnu.trove.TIntObjectHashMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javolution.util.FastList;
import javolution.util.FastMap;
import com.l2jserver.L2DatabaseFactory;
import com.l2jserver.gameserver.model.L2PledgeSkillLearn;
import com.l2jserver.gameserver.model.L2Skill;
import com.l2jserver.gameserver.model.L2SkillLearn;
import com.l2jserver.gameserver.model.L2TransformSkillLearn;
import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
import com.l2jserver.gameserver.model.base.ClassId;
import com.l2jserver.gameserver.model.base.Race;
/**
* This class ...
*
* @version $Revision: 1.13.2.2.2.8 $ $Date: 2005/04/06 16:13:25 $
*/
public class SkillTreeTable
{
private static Logger _log = Logger.getLogger(SkillTreeTable.class.getName());
private Map> _skillTrees;
private List _fishingSkillTrees; //all common skills (teached by Fisherman)
private List _expandDwarfCraftSkillTrees; //list of special skill for dwarf (expand dwarf craft) learned by class teacher
private List _pledgeSkillTrees; //pledge skill list
private List _TransformSkillTrees; // Transform Skills (Test)
private FastList _specialSkillTrees;
// checker, sorted arrays of hashcodes
private TIntObjectHashMap _skillsByClassIdHashCodes; // occupation skills
private TIntObjectHashMap _skillsByRaceHashCodes; // race-specific transformations
private int[] _allSkillsHashCodes; // fishing, special and all races transformations
private boolean _loading = true;
public static SkillTreeTable getInstance()
{
return SingletonHolder._instance;
}
private SkillTreeTable()
{
load();
}
/**
* Return the minimum level needed to have this Expertise.
*
* @param grade The grade level searched
*/
public int getExpertiseLevel(int grade)
{
if (grade <= 0)
return 0;
// since expertise comes at same level for all classes we use paladin for now
Map learnMap = getSkillTrees().get(ClassId.paladin);
int skillHashCode = SkillTable.getSkillHashCode(239, grade);
if (learnMap.containsKey(skillHashCode))
{
return learnMap.get(skillHashCode).getMinLevel();
}
_log.severe("Expertise not found for grade " + grade);
return 0;
}
/**
* Each class receives new skill on certain levels, this methods allow the retrieval of the minimun character level
* of given class required to learn a given skill
* @param skillId The iD of the skill
* @param classID The classId of the character
* @param skillLvl The SkillLvl
* @return The min level
*/
public int getMinSkillLevel(int skillId, ClassId classId, int skillLvl)
{
Map map = getSkillTrees().get(classId);
int skillHashCode = SkillTable.getSkillHashCode(skillId, skillLvl);
if (map.containsKey(skillHashCode))
{
return map.get(skillHashCode).getMinLevel();
}
return 0;
}
public int getMinSkillLevel(int skillId, int skillLvl)
{
int skillHashCode = SkillTable.getSkillHashCode(skillId, skillLvl);
// Look on all classes for this skill (takes the first one found)
for (Map map : getSkillTrees().values())
{
// checks if the current class has this skill
if (map.containsKey(skillHashCode))
{
return map.get(skillHashCode).getMinLevel();
}
}
return 0;
}
private void load()
{
_loading = true;
int classId = 0;
int count = 0;
Connection con = null;
try
{
con = L2DatabaseFactory.getInstance().getConnection();
try
{
PreparedStatement statement = con.prepareStatement("SELECT * FROM class_list ORDER BY id");
ResultSet classlist = statement.executeQuery();
Map map;
int parentClassId;
L2SkillLearn skillLearn;
PreparedStatement statement2 = con.prepareStatement("SELECT class_id, skill_id, level, name, sp, min_level, learned_by_npc, learned_by_fs, is_transfer, is_autoget FROM skill_trees where class_id=? ORDER BY skill_id, level");
while (classlist.next())
{
map = new FastMap();
parentClassId = classlist.getInt("parent_id");
classId = classlist.getInt("id");
statement2.setInt(1, classId);
ResultSet skilltree = statement2.executeQuery();
statement2.clearParameters();
if (parentClassId != -1)
{
Map parentMap = getSkillTrees().get(ClassId.values()[parentClassId]);
map.putAll(parentMap);
}
int prevSkillId = -1;
while (skilltree.next())
{
int id = skilltree.getInt("skill_id");
int lvl = skilltree.getInt("level");
String name = skilltree.getString("name");
int minLvl = skilltree.getInt("min_level");
int cost = skilltree.getInt("sp");
boolean npc = skilltree.getBoolean("learned_by_npc");
boolean fs = skilltree.getBoolean("learned_by_fs");
boolean trans = skilltree.getBoolean("is_transfer");
boolean autoget = skilltree.getBoolean("is_autoget");
if (prevSkillId != id)
prevSkillId = id;
skillLearn = new L2SkillLearn(id, lvl, minLvl, name, cost, 0, 0, npc, fs, trans, autoget);
map.put(SkillTable.getSkillHashCode(id, lvl), skillLearn);
}
getSkillTrees().put(ClassId.values()[classId], map);
skilltree.close();
count += map.size();
_log.fine("SkillTreeTable: skill tree for class " + classId + " has " + map.size() + " skills");
}
classlist.close();
statement.close();
statement2.close();
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error while creating skill tree (Class ID " + classId + "): " + e.getMessage(), e);
}
_log.info("SkillTreeTable: Loaded " + count + " skills.");
//Skill tree for fishing skill (from Fisherman)
try
{
_fishingSkillTrees = new FastList();
_expandDwarfCraftSkillTrees = new FastList();
PreparedStatement statement = con.prepareStatement("SELECT skill_id, level, name, sp, min_level, costid, cost, is_for_dwarf, learned_by_npc, learned_by_fs FROM fishing_skill_trees ORDER BY skill_id, level");
ResultSet skilltree2 = statement.executeQuery();
int prevSkillId = -1;
while (skilltree2.next())
{
int id = skilltree2.getInt("skill_id");
int lvl = skilltree2.getInt("level");
String name = skilltree2.getString("name");
int minLvl = skilltree2.getInt("min_level");
int cost = skilltree2.getInt("sp");
int costId = skilltree2.getInt("costid");
int costCount = skilltree2.getInt("cost");
boolean isDwarven = skilltree2.getBoolean("is_for_dwarf");
boolean npc = skilltree2.getBoolean("learned_by_npc");
boolean fs = skilltree2.getBoolean("learned_by_fs");
if (prevSkillId != id)
prevSkillId = id;
L2SkillLearn skill = new L2SkillLearn(id, lvl, minLvl, name, cost, costId, costCount, npc, fs, false, false);
if (isDwarven)
_expandDwarfCraftSkillTrees.add(skill);
else
_fishingSkillTrees.add(skill);
}
skilltree2.close();
statement.close();
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error while creating fishing skill table: " + e.getMessage(), e);
}
try
{
_pledgeSkillTrees = new FastList();
PreparedStatement statement = con.prepareStatement("SELECT skill_id, level, name, clan_lvl, repCost, itemId, itemCount FROM pledge_skill_trees ORDER BY skill_id, level");
ResultSet skilltree4 = statement.executeQuery();
int prevSkillId = -1;
while (skilltree4.next())
{
int id = skilltree4.getInt("skill_id");
int lvl = skilltree4.getInt("level");
String name = skilltree4.getString("name");
int baseLvl = skilltree4.getInt("clan_lvl");
int sp = skilltree4.getInt("repCost");
int itemId = skilltree4.getInt("itemId");
int itemCount = skilltree4.getInt("itemCount");
if (prevSkillId != id)
prevSkillId = id;
L2PledgeSkillLearn skill = new L2PledgeSkillLearn(id, lvl, baseLvl, name, sp, itemId, itemCount);
_pledgeSkillTrees.add(skill);
}
skilltree4.close();
statement.close();
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error while creating pledge skill table: " + e.getMessage(), e);
}
try
{
_TransformSkillTrees = new FastList();
PreparedStatement statement = con.prepareStatement("SELECT race_id, skill_id, item_id, level, name, sp, min_level FROM transform_skill_trees ORDER BY race_id, skill_id, level");
ResultSet skilltree5 = statement.executeQuery();
int prevSkillId = -1;
while (skilltree5.next())
{
int race_id = skilltree5.getInt("race_id");
int skill_id = skilltree5.getInt("skill_id");
int item_id = skilltree5.getInt("item_id");
int level = skilltree5.getInt("level");
String name = skilltree5.getString("name");
int sp = skilltree5.getInt("sp");
int min_level = skilltree5.getInt("min_level");
if (prevSkillId != skill_id)
prevSkillId = skill_id;
L2TransformSkillLearn skill = new L2TransformSkillLearn(race_id, skill_id, item_id, level, name, sp, min_level);
_TransformSkillTrees.add(skill);
}
skilltree5.close();
statement.close();
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error while creating Transformation skill table ", e);
}
try
{
_specialSkillTrees = new FastList();
PreparedStatement statement = con.prepareStatement("SELECT skill_id, level, name, costid, cost, learned_by_npc, learned_by_fs FROM special_skill_trees ORDER BY skill_id, level");
ResultSet skilltree6 = statement.executeQuery();
int prevSkillId = -1;
while (skilltree6.next())
{
int id = skilltree6.getInt("skill_id");
int lvl = skilltree6.getInt("level");
String name = skilltree6.getString("name");
int costId = skilltree6.getInt("costid");
int costCount = skilltree6.getInt("cost");
boolean npc = skilltree6.getBoolean("learned_by_npc");
boolean fs = skilltree6.getBoolean("learned_by_fs");
if (prevSkillId != id)
prevSkillId = id;
L2SkillLearn skill = new L2SkillLearn(id, lvl, 0, name, 0, costId, costCount, npc, fs, false, false);
_specialSkillTrees.add(skill);
}
skilltree6.close();
statement.close();
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error while creating special skill table: " + e.getMessage(), e);
}
}
catch (Exception e)
{
_log.log(Level.SEVERE, "Error while skill tables ", e);
}
finally
{
L2DatabaseFactory.close(con);
}
generateCheckArrays();
_log.info("FishingSkillTreeTable: Loaded " + _fishingSkillTrees.size() + " general skills.");
_log.info("DwarvenCraftSkillTreeTable: Loaded " + _expandDwarfCraftSkillTrees.size() + " dwarven skills.");
_log.info("PledgeSkillTreeTable: Loaded " + _pledgeSkillTrees.size() + " pledge skills");
_log.info("TransformSkillTreeTable: Loaded " + _TransformSkillTrees.size() + " transform skills");
_log.info("SpecialSkillTreeTable: Loaded " + _specialSkillTrees.size() + " special skills");
_loading = false;
}
private void generateCheckArrays()
{
int i;
int[] array;
// class-specific skills
Map tempMap;
TIntObjectHashMap result = new TIntObjectHashMap(_skillTrees.keySet().size());
for (ClassId cls : _skillTrees.keySet())
{
i = 0;
tempMap = _skillTrees.get(cls);
array = new int[tempMap.size()];
for (int h : tempMap.keySet())
array[i++] = h;
Arrays.sort(array);
result.put(cls.ordinal(), array);
}
_skillsByClassIdHashCodes = result;
// race-specific skills including dwarven (obtained by fishing)
FastList list = FastList.newInstance();
result = new TIntObjectHashMap(Race.values().length);
for (Race r : Race.values())
{
for (L2TransformSkillLearn s : _TransformSkillTrees)
if (s.getRace() == r.ordinal())
list.add(SkillTable.getSkillHashCode(s.getId(), s.getLevel()));
if (r == Race.Dwarf)
{
for (L2SkillLearn s : _expandDwarfCraftSkillTrees)
list.add(SkillTable.getSkillHashCode(s.getId(), s.getLevel()));
}
i = 0;
array = new int[list.size()];
for (int s : list)
array[i++] = s;
Arrays.sort(array);
result.put(r.ordinal(), array);
list.clear();
}
_skillsByRaceHashCodes = result;
// skills available for all classes and races
for (L2SkillLearn s : _fishingSkillTrees)
list.add(SkillTable.getSkillHashCode(s.getId(), s.getLevel()));
for (L2TransformSkillLearn s : _TransformSkillTrees)
if (s.getRace() == -1)
list.add(SkillTable.getSkillHashCode(s.getId(), s.getLevel()));
for (L2SkillLearn s : _specialSkillTrees)
list.add(SkillTable.getSkillHashCode(s.getId(), s.getLevel()));
i = 0;
array = new int[list.size()];
for (int s : list)
array[i++] = s;
Arrays.sort(array);
_allSkillsHashCodes = array;
FastList.recycle(list);
}
private Map> getSkillTrees()
{
if (_skillTrees == null)
_skillTrees = new FastMap>();
return _skillTrees;
}
public L2SkillLearn[] getAvailableSkills(L2PcInstance cha, ClassId classId)
{
List result = new FastList();
Collection skills = getSkillTrees().get(classId).values();
if (skills == null)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("Skilltree for class " + classId + " is not defined !");
return new L2SkillLearn[0];
}
L2Skill[] oldSkills = cha.getAllSkills();
for (L2SkillLearn temp : skills)
{
//Let's get all auto-get skills and all skill learn from npc, but transfer skills.
if ((temp.isAutoGetSkill() || (temp.isLearnedByNPC() && !temp.isTransferSkill())) && (temp.getMinLevel() <= cha.getLevel()))
{
boolean knownSkill = false;
for (int j = 0; j < oldSkills.length && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == temp.getLevel() - 1)
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && temp.getLevel() == 1)
{
// this is a new skill
result.add(temp);
}
}
}
return result.toArray(new L2SkillLearn[result.size()]);
}
public L2SkillLearn[] getAvailableSkills(L2PcInstance cha)
{
List result = new FastList();
List skills = new FastList();
skills.addAll(_fishingSkillTrees);
if (skills.size() < 1)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("Skilltree for fishing is not defined !");
return new L2SkillLearn[0];
}
if (cha.hasDwarvenCraft() && _expandDwarfCraftSkillTrees != null)
{
skills.addAll(_expandDwarfCraftSkillTrees);
}
L2Skill[] oldSkills = cha.getAllSkills();
for (L2SkillLearn temp : skills)
{
if (temp.isLearnedByNPC() && temp.getMinLevel() <= cha.getLevel())
{
boolean knownSkill = false;
for (int j = 0; j < oldSkills.length && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == temp.getLevel() - 1)
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && temp.getLevel() == 1)
{
// this is a new skill
result.add(temp);
}
}
}
return result.toArray(new L2SkillLearn[result.size()]);
}
public L2SkillLearn[] getAvailableSpecialSkills(L2PcInstance cha)
{
List result = new FastList();
List skills = new FastList();
skills.addAll(_specialSkillTrees);
if (skills.size() < 1)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("Skilltree for special is not defined !");
return new L2SkillLearn[0];
}
L2Skill[] oldSkills = cha.getAllSkills();
for (L2SkillLearn temp : skills)
{
boolean knownSkill = false;
for (int j = 0; j < oldSkills.length && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == temp.getLevel() - 1)
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && temp.getLevel() == 1)
{
// this is a new skill
result.add(temp);
}
}
return result.toArray(new L2SkillLearn[result.size()]);
}
public L2TransformSkillLearn[] getAvailableTransformSkills(L2PcInstance cha)
{
List result = new FastList();
List skills = _TransformSkillTrees;
if (skills == null)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("No Transform skills defined!");
return new L2TransformSkillLearn[0];
}
L2Skill[] oldSkills = cha.getAllSkills();
for (L2TransformSkillLearn temp : skills)
{
if (temp.getMinLevel() <= cha.getLevel() && (temp.getRace() == cha.getRace().ordinal() || temp.getRace() == -1))
{
boolean knownSkill = false;
for (int j = 0; j < oldSkills.length && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == temp.getLevel() - 1)
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && temp.getLevel() == 1)
{
// this is a new skill
result.add(temp);
}
}
}
return result.toArray(new L2TransformSkillLearn[result.size()]);
}
public L2PledgeSkillLearn[] getAvailablePledgeSkills(L2PcInstance cha)
{
List result = new FastList();
List skills = _pledgeSkillTrees;
if (skills == null)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("No clan skills defined!");
return new L2PledgeSkillLearn[0];
}
L2Skill[] oldSkills = cha.getClan().getAllSkills();
for (L2PledgeSkillLearn temp : skills)
{
if (temp.getBaseLevel() <= cha.getClan().getLevel())
{
boolean knownSkill = false;
for (int j = 0; j < oldSkills.length && !knownSkill; j++)
{
if (oldSkills[j].getId() == temp.getId())
{
knownSkill = true;
if (oldSkills[j].getLevel() == temp.getLevel() - 1)
{
// this is the next level of a skill that we know
result.add(temp);
}
}
}
if (!knownSkill && temp.getLevel() == 1)
{
// this is a new skill
result.add(temp);
}
}
}
return result.toArray(new L2PledgeSkillLearn[result.size()]);
}
/**
* Returns all allowed skills for a given class.
* @param classId
* @return all allowed skills for a given class.
*/
public Collection getAllowedSkills(ClassId classId)
{
return getSkillTrees().get(classId).values();
}
public int getMinLevelForNewSkill(L2PcInstance cha, ClassId classId)
{
int minLevel = 0;
Collection skills = getSkillTrees().get(classId).values();
if (skills == null)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("Skilltree for class " + classId + " is not defined !");
return minLevel;
}
for (L2SkillLearn temp : skills)
{
if (temp.getMinLevel() > cha.getLevel() && temp.getSpCost() != 0)
if (minLevel == 0 || temp.getMinLevel() < minLevel)
minLevel = temp.getMinLevel();
}
return minLevel;
}
public int getMinLevelForNewSkill(L2PcInstance cha)
{
int minLevel = 0;
List skills = new FastList();
skills.addAll(_fishingSkillTrees);
if (skills.size() < 1)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("SkillTree for fishing is not defined !");
return minLevel;
}
if (cha.hasDwarvenCraft() && _expandDwarfCraftSkillTrees != null)
{
skills.addAll(_expandDwarfCraftSkillTrees);
}
for (L2SkillLearn s : skills)
{
if (s.getMinLevel() > cha.getLevel())
if (minLevel == 0 || s.getMinLevel() < minLevel)
minLevel = s.getMinLevel();
}
return minLevel;
}
public int getMinLevelForNewTransformSkill(L2PcInstance cha)
{
int minLevel = 0;
List skills = new FastList();
skills.addAll(_TransformSkillTrees);
if (skills.size() < 1)
{
// the skilltree for this class is undefined, so we give an empty list
_log.warning("SkillTree for fishing is not defined !");
return minLevel;
}
for (L2TransformSkillLearn s : skills)
{
if ((s.getMinLevel() > cha.getLevel()) && (s.getRace() == cha.getRace().ordinal()))
if (minLevel == 0 || s.getMinLevel() < minLevel)
minLevel = s.getMinLevel();
}
return minLevel;
}
public int getSkillCost(L2PcInstance player, L2Skill skill)
{
int skillCost = 100000000;
ClassId classId = player.getSkillLearningClassId();
int skillHashCode = SkillTable.getSkillHashCode(skill);
if (getSkillTrees().get(classId).containsKey(skillHashCode))
{
L2SkillLearn skillLearn = getSkillTrees().get(classId).get(skillHashCode);
if (skillLearn.getMinLevel() <= player.getLevel())
{
skillCost = skillLearn.getSpCost();
if (!player.getClassId().equalsOrChildOf(classId))
return skillCost;
}
}
return skillCost;
}
public L2SkillLearn getSkillLearnBySkillIdLevel(ClassId classId, int skillId, int skillLvl)
{
for (L2SkillLearn sl : getAllowedSkills(classId))
{
if (sl.getId() == skillId && sl.getLevel() == skillLvl)
{
return sl; // found skill learn
}
}
return null;
}
public List getAllAllowedSkillId(L2PcInstance player)
{
FastList skills = new FastList();
for (L2SkillLearn tmp : getAllowedSkills(player.getClassId()))
{
if (skills.contains(tmp.getId()))
skills.add(tmp.getId());
}
return skills;
}
public boolean isSkillAllowed(L2PcInstance player, L2Skill skill)
{
if (skill.isExcludedFromCheck())
return true;
if (player.isGM() && skill.isGMSkill())
return true;
if (_loading) // prevent accidental skill remove during reload
return true;
final int maxLvl = SkillTable.getInstance().getMaxLevel(skill.getId());
final int hashCode = SkillTable.getSkillHashCode(skill.getId(), Math.min(skill.getLevel(), maxLvl));
if (Arrays.binarySearch(_skillsByClassIdHashCodes.get(player.getClassId().ordinal()), hashCode) >= 0)
return true;
if (Arrays.binarySearch(_skillsByRaceHashCodes.get(player.getRace().ordinal()), hashCode) >= 0)
return true;
if (Arrays.binarySearch(_allSkillsHashCodes, hashCode) >= 0)
return true;
return false;
}
@SuppressWarnings("synthetic-access")
private static class SingletonHolder
{
protected static final SkillTreeTable _instance = new SkillTreeTable();
}
public void reload()
{
load();
}
}