Java tutorial
/* * 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 com.l2jfree.gameserver.datatables; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javolution.util.FastSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.l2jfree.gameserver.document.DocumentEngine; import com.l2jfree.gameserver.model.skills.L2Skill; import com.l2jfree.gameserver.model.skills.l2skills.L2SkillLearnSkill; import com.l2jfree.gameserver.model.skills.learn.L2EnchantSkillLearn; import com.l2jfree.gameserver.model.skills.learn.L2EnchantSkillLearn.EnchantSkillDetail; import com.l2jfree.lang.L2Integer; public final class SkillTable { private static final Log _log = LogFactory.getLog(SkillTable.class); private static final class SingletonHolder { private static SkillTable INSTANCE = new SkillTable(); } public static SkillTable getInstance() { return SingletonHolder.INSTANCE; } public synchronized static void reload() { SingletonHolder.INSTANCE = new SkillTable(); } public static final class SkillInfo { private final Integer _skillId; private final int _level; private L2Skill _skill; private SkillInfo(int skillId, int level) { _skillId = L2Integer.valueOf(skillId); _level = level; } public Integer getId() { return _skillId; } public int getLevel() { return _level; } public L2Skill getSkill() { return _skill; } } private static SkillInfo[][] SKILL_INFOS = new SkillInfo[0][]; private final L2Skill[][] _skillTable; private final int[] _maxLevels; private final Set<L2Skill> _learnedSkills = new HashSet<L2Skill>(); private SkillTable() { final List<L2Skill> skills = DocumentEngine.loadSkills(); _log.info("SkillTable: Loaded " + skills.size() + " skill templates from XML files."); int highestId = 0; for (L2Skill skill : skills) if (highestId < skill.getId()) highestId = skill.getId(); _maxLevels = new int[highestId + 1]; int[] highestLevels = new int[highestId + 1]; for (L2Skill skill : skills) { if (highestLevels[skill.getId()] < skill.getLevel()) highestLevels[skill.getId()] = skill.getLevel(); if (_maxLevels[skill.getId()] < skill.getLevel() && skill.getLevel() < 100) _maxLevels[skill.getId()] = skill.getLevel(); } // clear previously stored skills for (SkillInfo[] infos : SKILL_INFOS) if (infos != null) for (SkillInfo info : infos) if (info != null) info._skill = null; _skillTable = new L2Skill[highestId + 1][]; SKILL_INFOS = Arrays.copyOf(SKILL_INFOS, Math.max(SKILL_INFOS.length, highestId + 1)); for (int i = 0; i < highestLevels.length; i++) { final int highestLevel = highestLevels[i]; if (highestLevel < 1) continue; _skillTable[i] = new L2Skill[highestLevel + 1]; if (SKILL_INFOS[i] == null) SKILL_INFOS[i] = new SkillInfo[highestLevel + 1]; else SKILL_INFOS[i] = Arrays.copyOf(SKILL_INFOS[i], Math.max(SKILL_INFOS[i].length, highestLevel + 1)); } for (L2Skill skill : skills) { _skillTable[skill.getId()][skill.getLevel()] = skill; if (SKILL_INFOS[skill.getId()][skill.getLevel()] == null) SKILL_INFOS[skill.getId()][skill.getLevel()] = new SkillInfo(skill.getId(), skill.getLevel()); SKILL_INFOS[skill.getId()][skill.getLevel()]._skill = skill; } int length = _skillTable.length; for (L2Skill[] array : _skillTable) if (array != null) length += array.length; _log.info("SkillTable: Occupying arrays for " + length + "."); SingletonHolder.INSTANCE = this; Map<Integer, L2Skill> skillsByUID = new HashMap<Integer, L2Skill>(); for (L2Skill skill : skills) { try { L2Skill old = skillsByUID.put(SkillTable.getSkillUID(skill), skill); if (old != null) _log.warn("Overlapping UIDs for: " + old + ", " + skill, new IllegalStateException()); skill.validate(); } catch (Exception e) { _log.warn(skill, e); } } for (L2Skill skill0 : skills) { if (!(skill0 instanceof L2SkillLearnSkill)) continue; L2SkillLearnSkill skill = (L2SkillLearnSkill) skill0; for (int i = 0; i < skill.getNewSkillId().length; i++) { final L2Skill learnedSkill = getInfo(skill.getNewSkillId()[i], skill.getNewSkillLvl()[i]); if (learnedSkill != null) _learnedSkills.add(learnedSkill); } } // checking for skill enchantment mismatch // in XMLs final TreeSet<String> skillEnchantsInXMLs = new TreeSet<String>(); // reusing final Map<Integer, Set<Integer>> enchantLevelsByEnchantType = new HashMap<Integer, Set<Integer>>(); for (int skillId = 0; skillId < _skillTable.length; skillId++) { final L2Skill[] skillsById = _skillTable[skillId]; if (skillsById == null) continue; for (final L2Skill skill : skillsById) { if (skill == null || skill.getLevel() < 100) continue; final int enchantType = skill.getLevel() / 100; final int enchantLevel = skill.getLevel() % 100; Set<Integer> enchantLevels = enchantLevelsByEnchantType.get(enchantType); if (enchantLevels == null) enchantLevelsByEnchantType.put(enchantType, enchantLevels = new FastSet<Integer>(30)); enchantLevels.add(enchantLevel); } for (Map.Entry<Integer, Set<Integer>> entry : enchantLevelsByEnchantType.entrySet()) { final int enchantType = entry.getKey(); final Set<Integer> enchantLevels = entry.getValue(); if (enchantLevels.isEmpty()) continue; final String s = "Skill ID: " + skillId + " - EnchantType: enchant" + enchantType + " - Levels: " + enchantLevels.size(); boolean valid = true; for (int skillLvl = 1; skillLvl <= 30; skillLvl++) { if (!enchantLevels.remove(skillLvl)) { if (skillLvl == 16 && enchantLevels.isEmpty()) break; _log.warn("Missing skill enchant level in XMLs for " + s + " - Level: " + skillLvl); valid = false; } } if (!enchantLevels.isEmpty()) _log.warn("Extra skill enchant levels in XMLs for " + s + " - Levels: " + enchantLevels); else if (valid) skillEnchantsInXMLs.add(s); // reusing enchantLevels.clear(); } } // in database final TreeSet<String> skillEnchantsInDatabase = new TreeSet<String>(); for (L2EnchantSkillLearn skillLearn : SkillTreeTable.getInstance().getSkillEnchantments()) { final int skillId = skillLearn.getId(); final List<EnchantSkillDetail>[] details = skillLearn.getEnchantRoutes(); if (details.length == 0) _log.warn("Invalid skill enchant data in database for Skill ID: " + skillId); for (int indexingEnchantType = 0; indexingEnchantType < details.length; indexingEnchantType++) { final List<EnchantSkillDetail> route = details[indexingEnchantType]; if (route == null) continue; final String s = "Skill ID: " + skillId + " - EnchantType: enchant" + (indexingEnchantType + 1) + " - Levels: " + route.size(); if (route.size() != 30 && route.size() != 15) _log.warn("Invalid skill enchant data in database for " + s); else skillEnchantsInDatabase.add(s); } } // comparing the results for (String skillEnchant : skillEnchantsInXMLs) if (!skillEnchantsInDatabase.remove(skillEnchant)) _log.warn("Missing skill enchant data in database for " + skillEnchant); for (String skillEnchant : skillEnchantsInDatabase) _log.warn("Missing skill enchant data in XMLs for " + skillEnchant); // just validation for (L2EnchantSkillLearn skillLearn : SkillTreeTable.getInstance().getSkillEnchantments()) { final int skillId = skillLearn.getId(); final List<EnchantSkillDetail>[] details = skillLearn.getEnchantRoutes(); final int maxLevel = getMaxLevel(skillId); if (skillLearn.getBaseLevel() != maxLevel) _log.warn("Invalid `base_lvl` skill enchant data in database for Skill ID: " + skillId); for (int indexingEnchantType = 0; indexingEnchantType < details.length; indexingEnchantType++) { final List<EnchantSkillDetail> route = details[indexingEnchantType]; if (route == null) continue; final String s = "Skill ID: " + skillId + " - EnchantType: enchant" + (indexingEnchantType + 1) + " - Levels: " + route.size(); int index = 1; int expectedMinSkillLevel = maxLevel; for (EnchantSkillDetail detail : route) { if (detail.getLevel() % 100 != index) _log.warn("Invalid `level` skill enchant data in database for " + s); if (detail.getMinSkillLevel() != expectedMinSkillLevel) _log.warn("Invalid `min_skill_lvl` skill enchant data in database for " + s); index++; expectedMinSkillLevel = detail.getLevel(); } } } } public static int getSkillUID(L2Skill skill) { return skill == null ? 0 : getSkillUID(skill.getId(), skill.getLevel()); } public static int getSkillUID(int skillId, int skillLevel) { return skillId * 1023 + skillLevel; } public L2Skill getInfo(final int skillId, final int level) { // there is no skill with non-positive level if (level < 1) return null; // TODO: warn // there is no skill at all with that id if (skillId < 1 || _skillTable.length <= skillId) return null; // TODO: warn final L2Skill[] array = _skillTable[skillId]; // there is no skill at all with that id if (array == null) return null; // TODO: warn // skill maybe exists with the given level if (level < array.length) { final L2Skill skill = array[level]; // and yes it do exists if (skill != null) return skill; } // if not, then skill with max level return array[_maxLevels[skillId]]; // TODO: warn } public SkillInfo getSkillInfo(int skillId, int level) { if (skillId < 1 || SKILL_INFOS.length <= skillId) return null; SkillInfo[] array = SKILL_INFOS[skillId]; if (array == null) return null; if (level < 1 || array.length <= level) return null; return array[level]; } public int getMaxLevel(int skillId) { if (skillId < 1 || _maxLevels.length <= skillId) return 0; return _maxLevels[skillId]; } public int getNormalLevel(L2Skill skill) { if (skill.getLevel() < 100) return skill.getLevel(); return getMaxLevel(skill.getId()); } public L2Skill[] getSiegeSkills(boolean addNoble) { final L2Skill[] skills = new L2Skill[addNoble ? 3 : 2]; skills[0] = getInfo(246, 1); skills[1] = getInfo(247, 1); if (addNoble) skills[2] = getInfo(326, 1); return skills; } public boolean isLearnedSkill(L2Skill skill) { return _learnedSkills.contains(skill); } }