/* 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 2, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* http://www.gnu.org/copyleft/gpl.html
*/
package com.l2jfrozen.gameserver.datatables.xml;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javolution.util.FastList;
import javolution.util.FastMap;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.l2jfrozen.Config;
import com.l2jfrozen.gameserver.datatables.SkillTable;
import com.l2jfrozen.gameserver.model.L2Augmentation;
import com.l2jfrozen.gameserver.model.L2Skill;
import com.l2jfrozen.gameserver.model.actor.instance.L2ItemInstance;
import com.l2jfrozen.gameserver.skills.Stats;
import com.l2jfrozen.util.random.Rnd;
/**
* This class manages the augmentation data and can also create new augmentations.
*
* @author programmos & Incognito, sword dev
*/
public class AugmentationData
{
private static final Logger _log = Logger.getLogger(AugmentationData.class.getName());
// =========================================================
private static AugmentationData _instance;
public static final AugmentationData getInstance()
{
if(_instance == null)
{
_instance = new AugmentationData();
}
return _instance;
}
// =========================================================
// Data Field
// stats
private static final int STAT_START = 1;
private static final int STAT_END = 14560;
private static final int STAT_BLOCKSIZE = 3640;
//private static final int STAT_NUMBEROF_BLOCKS = 4;
private static final int STAT_SUBBLOCKSIZE = 91;
//private static final int STAT_NUMBEROF_SUBBLOCKS = 40;
// skills
private static final int BLUE_START = 14561;
//private static final int PURPLE_START = 14578;
//private static final int RED_START = 14685;
private static final int SKILLS_BLOCKSIZE = 178;
// basestats
private static final int BASESTAT_STR = 16341;
private static final int BASESTAT_CON = 16342;
private static final int BASESTAT_INT = 16343;
private static final int BASESTAT_MEN = 16344;
private FastList<augmentationStat> _augmentationStats[];
private Map<Integer, FastList<augmentationSkill>> _blueSkills;
private Map<Integer, FastList<augmentationSkill>> _purpleSkills;
private Map<Integer, FastList<augmentationSkill>> _redSkills;
// =========================================================
// Constructor
@SuppressWarnings("unchecked")
private AugmentationData()
{
_log.info("Initializing AugmentationData.");
_augmentationStats = new FastList[4];
_augmentationStats[0] = new FastList<augmentationStat>();
_augmentationStats[1] = new FastList<augmentationStat>();
_augmentationStats[2] = new FastList<augmentationStat>();
_augmentationStats[3] = new FastList<augmentationStat>();
_blueSkills = new FastMap<Integer, FastList<augmentationSkill>>();
_purpleSkills = new FastMap<Integer, FastList<augmentationSkill>>();
_redSkills = new FastMap<Integer, FastList<augmentationSkill>>();
for(int i = 1; i <= 10; i++)
{
_blueSkills.put(i, new FastList<augmentationSkill>());
_purpleSkills.put(i, new FastList<augmentationSkill>());
_redSkills.put(i, new FastList<augmentationSkill>());
}
load();
// Use size*4: since theres 4 blocks of stat-data with equivalent size
_log.info("AugmentationData: Loaded: " + _augmentationStats[0].size() * 4 + " augmentation stats.");
if(Config.DEBUG)
{
for(int i = 1; i <= 10; i++)
{
_log.info("AugmentationData: Loaded: " + _blueSkills.get(i).size() + " blue, " + _purpleSkills.get(i).size() + " purple and " + _redSkills.get(i).size() + " red skills for lifeStoneLevel " + i);
}
}
}
public static void reload(){
_instance = null;
getInstance();
}
// =========================================================
// Nested Class
public class augmentationSkill
{
private int _skillId;
private int _maxSkillLevel;
private int _augmentationSkillId;
public augmentationSkill(int skillId, int maxSkillLevel, int augmentationSkillId)
{
_skillId = skillId;
_maxSkillLevel = maxSkillLevel;
_augmentationSkillId = augmentationSkillId;
}
public L2Skill getSkill(int level)
{
if(level > _maxSkillLevel)
return SkillTable.getInstance().getInfo(_skillId, _maxSkillLevel);
return SkillTable.getInstance().getInfo(_skillId, level);
}
public int getAugmentationSkillId()
{
return _augmentationSkillId;
}
}
public class augmentationStat
{
private Stats _stat;
private int _singleSize;
private int _combinedSize;
private float _singleValues[];
private float _combinedValues[];
public augmentationStat(Stats stat, float sValues[], float cValues[])
{
_stat = stat;
_singleSize = sValues.length;
_singleValues = sValues;
_combinedSize = cValues.length;
_combinedValues = cValues;
}
public int getSingleStatSize()
{
return _singleSize;
}
public int getCombinedStatSize()
{
return _combinedSize;
}
public float getSingleStatValue(int i)
{
if(i >= _singleSize || i < 0)
return _singleValues[_singleSize - 1];
return _singleValues[i];
}
public float getCombinedStatValue(int i)
{
if(i >= _combinedSize || i < 0)
return _combinedValues[_combinedSize - 1];
return _combinedValues[i];
}
public Stats getStat()
{
return _stat;
}
}
// =========================================================
// Method - Private
private final void load()
{
// Load the skillmap
// Note: the skillmap data is only used when generating new augmentations
// the client expects a different id in order to display the skill in the
// items description...
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
int badAugmantData = 0;
File file = new File(Config.DATAPACK_ROOT + "/data/stats/augmentation/augmentation_skillmap.xml");
if(!file.exists())
{
if(Config.DEBUG)
{
System.out.println("The augmentation skillmap file is missing.");
}
return;
}
Document doc = factory.newDocumentBuilder().parse(file);
for(Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
{
if("list".equalsIgnoreCase(n.getNodeName()))
{
for(Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
{
if("augmentation".equalsIgnoreCase(d.getNodeName()))
{
NamedNodeMap attrs = d.getAttributes();
int skillId = 0, augmentationId = Integer.parseInt(attrs.getNamedItem("id").getNodeValue());
String type = "blue";
int skillLvL = 0;
for(Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if("skillId".equalsIgnoreCase(cd.getNodeName()))
{
attrs = cd.getAttributes();
skillId = Integer.parseInt(attrs.getNamedItem("val").getNodeValue());
}
else if("skillLevel".equalsIgnoreCase(cd.getNodeName()))
{
attrs = cd.getAttributes();
skillLvL = Integer.parseInt(attrs.getNamedItem("val").getNodeValue());
}
else if("type".equalsIgnoreCase(cd.getNodeName()))
{
attrs = cd.getAttributes();
type = attrs.getNamedItem("val").getNodeValue();
}
}
if(skillId == 0)
{
if(Config.DEBUG)
{
_log.log(Level.SEVERE, "Bad skillId in augmentation_skillmap.xml in the augmentationId:" + augmentationId);
}
badAugmantData++;
continue;
}
else if(skillLvL == 0)
{
if(Config.DEBUG)
{
_log.log(Level.SEVERE, "Bad skillLevel in augmentation_skillmap.xml in the augmentationId:" + augmentationId);
}
badAugmantData++;
continue;
}
int k = 1;
while(augmentationId - k * SKILLS_BLOCKSIZE >= BLUE_START)
{
k++;
}
if(type.equalsIgnoreCase("blue"))
{
_blueSkills.get(k).add(new augmentationSkill(skillId, skillLvL, augmentationId));
}
else if(type.equalsIgnoreCase("purple"))
{
_purpleSkills.get(k).add(new augmentationSkill(skillId, skillLvL, augmentationId));
}
else
{
_redSkills.get(k).add(new augmentationSkill(skillId, skillLvL, augmentationId));
}
attrs = null;
}
}
}
}
if(badAugmantData != 0)
{
_log.info("AugmentationData: " + badAugmantData + " bad skill(s) were skipped.");
}
doc = null;
file = null;
}
catch(Exception e)
{
if(Config.ENABLE_ALL_EXCEPTIONS)
e.printStackTrace();
_log.log(Level.SEVERE, "Error parsing augmentation_skillmap.xml.", e);
return;
}
// Load the stats from xml
for(int i = 1; i < 5; i++)
{
try
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setIgnoringComments(true);
File file = new File(Config.DATAPACK_ROOT + "/data/stats/augmentation/augmentation_stats" + i + ".xml");
if(!file.exists())
{
if(Config.DEBUG)
{
System.out.println("The augmentation stat data file " + i + " is missing.");
}
return;
}
Document doc = factory.newDocumentBuilder().parse(file);
for(Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
{
if("list".equalsIgnoreCase(n.getNodeName()))
{
for(Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
{
if("stat".equalsIgnoreCase(d.getNodeName()))
{
NamedNodeMap attrs = d.getAttributes();
String statName = attrs.getNamedItem("name").getNodeValue();
float soloValues[] = null, combinedValues[] = null;
for(Node cd = d.getFirstChild(); cd != null; cd = cd.getNextSibling())
{
if("table".equalsIgnoreCase(cd.getNodeName()))
{
attrs = cd.getAttributes();
String tableName = attrs.getNamedItem("name").getNodeValue();
StringTokenizer data = new StringTokenizer(cd.getFirstChild().getNodeValue());
List<Float> array = new FastList<Float>();
while(data.hasMoreTokens())
{
array.add(Float.parseFloat(data.nextToken()));
}
if(tableName.equalsIgnoreCase("#soloValues"))
{
soloValues = new float[array.size()];
int x = 0;
for(float value : array)
{
soloValues[x++] = value;
}
}
else
{
combinedValues = new float[array.size()];
int x = 0;
for(float value : array)
{
combinedValues[x++] = value;
}
}
tableName = null;
}
}
// store this stat
_augmentationStats[(i - 1)].add(new augmentationStat(Stats.valueOfXml(statName), soloValues, combinedValues));
statName = null;
attrs = null;
}
}
}
}
doc = null;
file = null;
factory = null;
}
catch(Exception e)
{
if(Config.ENABLE_ALL_EXCEPTIONS)
e.printStackTrace();
_log.log(Level.SEVERE, "Error parsing augmentation_stats" + i + ".xml.", e);
return;
}
}
}
// =========================================================
// Properties - Public
/**
* Generate a new random augmentation
*
* @param item
* @param lifeStoneLevel
* @param lifeStoneGrade
* @return L2Augmentation
*/
public L2Augmentation generateRandomAugmentation(L2ItemInstance item, int lifeStoneLevel, int lifeStoneGrade)
{
// Note that stat12 stands for stat 1 AND 2 (same for stat34 ;p )
// this is because a value can contain up to 2 stat modifications
// (there are two short values packed in one integer value, meaning 4 stat modifications at max)
// for more info take a look at getAugStatsById(...)
// Note: lifeStoneGrade: (0 means low grade, 3 top grade)
// First: determine whether we will add a skill/baseStatModifier or not because this determine which color could be the result
int skill_Chance = 0;
int stat34 = 0;
boolean generateSkill = false;
int resultColor = 0;
boolean generateGlow = false;
switch(lifeStoneGrade)
{
case 0:
skill_Chance = Config.AUGMENTATION_NG_SKILL_CHANCE;
if(Rnd.get(1, 100) <= Config.AUGMENTATION_NG_GLOW_CHANCE)
{
generateGlow = true;
}
break;
case 1:
skill_Chance = Config.AUGMENTATION_MID_SKILL_CHANCE;
if(Rnd.get(1, 100) <= Config.AUGMENTATION_MID_GLOW_CHANCE)
{
generateGlow = true;
}
break;
case 2:
skill_Chance = Config.AUGMENTATION_HIGH_SKILL_CHANCE;
if(Rnd.get(1, 100) <= Config.AUGMENTATION_HIGH_GLOW_CHANCE)
{
generateGlow = true;
}
break;
case 3:
skill_Chance = Config.AUGMENTATION_TOP_SKILL_CHANCE;
if(Rnd.get(1, 100) <= Config.AUGMENTATION_TOP_GLOW_CHANCE)
{
generateGlow = true;
}
}
if(Rnd.get(1, 100) <= skill_Chance)
{
generateSkill = true;
}
else if(Rnd.get(1, 100) <= Config.AUGMENTATION_BASESTAT_CHANCE)
{
stat34 = Rnd.get(BASESTAT_STR, BASESTAT_MEN);
}
// Second: decide which grade the augmentation result is going to have:
// 0:yellow, 1:blue, 2:purple, 3:red
// The chances used here are most likely custom,
// whats known is: u can also get a red result from a normal grade lifeStone
// however I will make it so that a higher grade lifeStone will more likely result in a
// higher grade augmentation... and the augmentation result will at least have the grade
// of the life stone
// Second: Calculate the subblock offset for the choosen color,
// and the level of the lifeStone
// whats known is: you cant have yellow with skill(or baseStatModifier)
// noGrade stone can not have glow, mid only with skill, high has a chance(custom), top allways glow
if(stat34 == 0 && !generateSkill)
{
resultColor = Rnd.get(0, 100);
if(resultColor <= 15 * lifeStoneGrade + 40)
{
resultColor = 1;
}
else
{
resultColor = 0;
}
}
else
{
resultColor = Rnd.get(0, 100);
if(resultColor <= 10 * lifeStoneGrade + 5 || stat34 != 0)
{
resultColor = 3;
}
else if(resultColor <= 10 * lifeStoneGrade + 10)
{
resultColor = 1;
}
else
{
resultColor = 2;
}
}
// is neither a skill nor basestat used for stat34? then generate a normal stat
int stat12 = 0;
if(stat34 == 0 && !generateSkill)
{
int temp = Rnd.get(2, 3);
int colorOffset = resultColor * 10 * STAT_SUBBLOCKSIZE + temp * STAT_BLOCKSIZE + 1;
int offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + colorOffset;
stat34 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
if(generateGlow && lifeStoneGrade >= 2)
{
offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + (temp - 2) * STAT_BLOCKSIZE + lifeStoneGrade * 10 * STAT_SUBBLOCKSIZE + 1;
}
else
{
offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + (temp - 2) * STAT_BLOCKSIZE + Rnd.get(0, 1) * 10 * STAT_SUBBLOCKSIZE + 1;
}
stat12 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
}
else
{
int offset;
if(!generateGlow)
{
offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + Rnd.get(0, 1) * STAT_BLOCKSIZE + 1;
}
else
{
offset = (lifeStoneLevel - 1) * STAT_SUBBLOCKSIZE + Rnd.get(0, 1) * STAT_BLOCKSIZE + (lifeStoneGrade + resultColor) / 2 * 10 * STAT_SUBBLOCKSIZE + 1;
}
stat12 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
}
// generate a skill if neccessary
L2Skill skill = null;
if(generateSkill)
{
augmentationSkill temp = null;
switch(resultColor)
{
case 1: // blue skill
temp = _blueSkills.get(lifeStoneLevel).get(Rnd.get(0, _blueSkills.get(lifeStoneLevel).size() - 1));
skill = temp.getSkill(lifeStoneLevel);
stat34 = temp.getAugmentationSkillId();
break;
case 2: // purple skill
temp = _purpleSkills.get(lifeStoneLevel).get(Rnd.get(0, _purpleSkills.get(lifeStoneLevel).size() - 1));
skill = temp.getSkill(lifeStoneLevel);
stat34 = temp.getAugmentationSkillId();
break;
case 3: // red skill
temp = _redSkills.get(lifeStoneLevel).get(Rnd.get(0, _redSkills.get(lifeStoneLevel).size() - 1));
skill = temp.getSkill(lifeStoneLevel);
stat34 = temp.getAugmentationSkillId();
break;
}
}
if(Config.DEBUG)
{
_log.info("Augmentation success: stat12=" + stat12 + "; stat34=" + stat34 + "; resultColor=" + resultColor + "; level=" + lifeStoneLevel + "; grade=" + lifeStoneGrade);
}
return new L2Augmentation(item, ((stat34 << 16) + stat12), skill, true);
}
public class AugStat
{
private Stats _stat;
private float _value;
public AugStat(Stats stat, float value)
{
_stat = stat;
_value = value;
}
public Stats getStat()
{
return _stat;
}
public float getValue()
{
return _value;
}
}
/**
* Returns the stat and basestat boni for a given augmentation id
*
* @param augmentationId
* @return
*/
public FastList<AugStat> getAugStatsById(int augmentationId)
{
FastList<AugStat> temp = new FastList<AugStat>();
// An augmentation id contains 2 short vaues so we gotta seperate them here
// both values contain a number from 1-16380, the first 14560 values are stats
// the 14560 stats are devided into 4 blocks each holding 3640 values
// each block contains 40 subblocks holding 91 stat values
// the first 13 values are so called Solo-stats and they have the highest stat increase possible
// after the 13 Solo-stats come 78 combined stats (thats every possible combination of the 13 solo stats)
// the first 12 combined stats (14-26) is the stat 1 combined with stat 2-13
// the next 11 combined stats then are stat 2 combined with stat 3-13 and so on...
// to get the idea have a look @ optiondata_client-e.dat - thats where the data came from :)
int stats[] = new int[2];
stats[0] = 0x0000FFFF & augmentationId;
stats[1] = augmentationId >> 16;
for(int i = 0; i < 2; i++)
{
// its a stat
if(stats[i] >= STAT_START && stats[i] <= STAT_END)
{
int block = 0;
while(stats[i] > STAT_BLOCKSIZE)
{
stats[i] -= STAT_BLOCKSIZE;
block++;
}
int subblock = 0;
while(stats[i] > STAT_SUBBLOCKSIZE)
{
stats[i] -= STAT_SUBBLOCKSIZE;
subblock++;
}
if(stats[i] < 14) // solo stat
{
augmentationStat as = _augmentationStats[block].get((stats[i] - 1));
temp.add(new AugStat(as.getStat(), as.getSingleStatValue(subblock)));
}
else
// twin stat
{
stats[i] -= 13; // rescale to 0 (if first of first combined block)
int x = 12; // next combi block has 12 stats
int rescales = 0; // number of rescales done
while(stats[i] > x)
{
stats[i] -= x;
x--;
rescales++;
}
// get first stat
augmentationStat as = _augmentationStats[block].get(rescales);
if(rescales == 0)
{
temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock)));
}
else
{
temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock * 2 + 1)));
}
// get 2nd stat
as = _augmentationStats[block].get(rescales + stats[i]);
if(as.getStat() == Stats.CRITICAL_DAMAGE)
{
temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock)));
}
else
{
temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(subblock * 2)));
}
as = null;
}
}
// its a base stat
else if(stats[i] >= BASESTAT_STR && stats[i] <= BASESTAT_MEN)
{
switch(stats[i])
{
case BASESTAT_STR:
temp.add(new AugStat(Stats.STAT_STR, 1.0f));
break;
case BASESTAT_CON:
temp.add(new AugStat(Stats.STAT_CON, 1.0f));
break;
case BASESTAT_INT:
temp.add(new AugStat(Stats.STAT_INT, 1.0f));
break;
case BASESTAT_MEN:
temp.add(new AugStat(Stats.STAT_MEN, 1.0f));
break;
}
}
}
return temp;
}
}