com.l2jfree.gameserver.datatables.AugmentationData.java Source code

Java tutorial

Introduction

Here is the source code for com.l2jfree.gameserver.datatables.AugmentationData.java

Source

/*
 * 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.io.File;
import java.util.ArrayList;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilderFactory;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import com.l2jfree.Config;
import com.l2jfree.gameserver.model.L2Augmentation;
import com.l2jfree.gameserver.model.items.templates.L2Item;
import com.l2jfree.gameserver.model.skills.L2Skill;
import com.l2jfree.gameserver.model.skills.Stats;
import com.l2jfree.gameserver.network.packets.client.AbstractRefinePacket;
import com.l2jfree.tools.random.Rnd;

/**
 * This class manages the augmentation data and can also create new augmentations.
 *
 * @author  durgus
 * edited by Gigiikun
 */
public class AugmentationData {
    private final static Log _log = LogFactory.getLog(AugmentationData.class);

    public static final AugmentationData getInstance() {
        return SingletonHolder._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;
    private static final int STAT_NUM = 13;

    private static final byte[] STATS1_MAP = new byte[STAT_SUBBLOCKSIZE];
    private static final byte[] STATS2_MAP = new byte[STAT_SUBBLOCKSIZE];

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

    // accessory
    private static final int ACC_START = 16669;
    private static final int ACC_BLOCKS_NUM = 10;
    private static final int ACC_STAT_SUBBLOCKSIZE = 21;
    private static final int ACC_STAT_NUM = 6;

    private static final int ACC_RING_START = ACC_START;
    private static final int ACC_RING_SKILLS = 18;
    private static final int ACC_RING_BLOCKSIZE = ACC_RING_SKILLS + 4 * ACC_STAT_SUBBLOCKSIZE;
    private static final int ACC_RING_END = ACC_RING_START + ACC_BLOCKS_NUM * ACC_RING_BLOCKSIZE - 1;

    private static final int ACC_EAR_START = ACC_RING_END + 1;
    private static final int ACC_EAR_SKILLS = 18;
    private static final int ACC_EAR_BLOCKSIZE = ACC_EAR_SKILLS + 4 * ACC_STAT_SUBBLOCKSIZE;
    private static final int ACC_EAR_END = ACC_EAR_START + ACC_BLOCKS_NUM * ACC_EAR_BLOCKSIZE - 1;

    private static final int ACC_NECK_START = ACC_EAR_END + 1;
    private static final int ACC_NECK_SKILLS = 24;
    private static final int ACC_NECK_BLOCKSIZE = ACC_NECK_SKILLS + 4 * ACC_STAT_SUBBLOCKSIZE;

    private static final int ACC_END = ACC_NECK_START + ACC_BLOCKS_NUM * ACC_NECK_BLOCKSIZE;

    private static final byte[] ACC_STATS1_MAP = new byte[ACC_STAT_SUBBLOCKSIZE];
    private static final byte[] ACC_STATS2_MAP = new byte[ACC_STAT_SUBBLOCKSIZE];

    private final ArrayList<?>[] _augStats = new ArrayList[4];
    private final ArrayList<?>[] _augAccStats = new ArrayList[4];

    private final ArrayList<?>[] _blueSkills = new ArrayList[10];
    private final ArrayList<?>[] _purpleSkills = new ArrayList[10];
    private final ArrayList<?>[] _redSkills = new ArrayList[10];
    private final ArrayList<?>[] _yellowSkills = new ArrayList[10];

    private final FastMap<Integer, augmentationSkill> _allSkills = new FastMap<Integer, augmentationSkill>()
            .setShared(true);

    // =========================================================
    // Constructor
    private AugmentationData() {
        _log.info("Initializing AugmentationData.");

        _augStats[0] = new ArrayList<augmentationStat>();
        _augStats[1] = new ArrayList<augmentationStat>();
        _augStats[2] = new ArrayList<augmentationStat>();
        _augStats[3] = new ArrayList<augmentationStat>();

        _augAccStats[0] = new ArrayList<augmentationStat>();
        _augAccStats[1] = new ArrayList<augmentationStat>();
        _augAccStats[2] = new ArrayList<augmentationStat>();
        _augAccStats[3] = new ArrayList<augmentationStat>();

        // Lookup tables structure: STAT1 represent first stat, STAT2 - second.
        // If both values are the same - use solo stat, if different - combined.
        byte idx;
        // weapon augmentation block: solo values first
        // 00-00, 01-01 ... 11-11,12-12
        for (idx = 0; idx < STAT_NUM; idx++) {
            // solo stats
            STATS1_MAP[idx] = idx;
            STATS2_MAP[idx] = idx;
        }
        // combined values next.
        // 00-01,00-02,00-03 ... 00-11,00-12;
        // 01-02,01-03 ... 01-11,01-12;
        // ...
        // 09-10,09-11,09-12;
        // 10-11,10-12;
        // 11-12
        for (int i = 0; i < STAT_NUM; i++) {
            for (int j = i + 1; j < STAT_NUM; idx++, j++) {
                // combined stats
                STATS1_MAP[idx] = (byte) i;
                STATS2_MAP[idx] = (byte) j;
            }
        }
        idx = 0;
        // accessory augmentation block, structure is different:
        // 00-00,00-01,00-02,00-03,00-04,00-05
        // 01-01,01-02,01-03,01-04,01-05
        // 02-02,02-03,02-04,02-05
        // 03-03,03-04,03-05
        // 04-04 \
        // 05-05 - order is changed here
        // 04-05 /
        // First values always solo, next are combined, except last 3 values
        for (int i = 0; i < ACC_STAT_NUM - 2; i++) {
            for (int j = i; j < ACC_STAT_NUM; idx++, j++) {
                ACC_STATS1_MAP[idx] = (byte) i;
                ACC_STATS2_MAP[idx] = (byte) j;
            }
        }
        ACC_STATS1_MAP[idx] = 4;
        ACC_STATS2_MAP[idx++] = 4;
        ACC_STATS1_MAP[idx] = 5;
        ACC_STATS2_MAP[idx++] = 5;
        ACC_STATS1_MAP[idx] = 4;
        ACC_STATS2_MAP[idx] = 5;

        for (int i = 0; i < 10; i++) {
            _blueSkills[i] = new ArrayList<Integer>();
            _purpleSkills[i] = new ArrayList<Integer>();
            _redSkills[i] = new ArrayList<Integer>();
            _yellowSkills[i] = new ArrayList<Integer>();
        }

        load();

        // Use size*4: since theres 4 blocks of stat-data with equivalent size
        _log.info("AugmentationData: Loaded: " + (_augStats[0].size() * 4) + " augmentation stats.");
        _log.info("AugmentationData: Loaded: " + (_augAccStats[0].size() * 4) + " accessory augmentation stats.");
        for (int i = 0; i < 10; i++) {
            _log.info("AugmentationData: Loaded: " + _blueSkills[i].size() + " blue, " + _purpleSkills[i].size()
                    + " purple and " + _redSkills[i].size() + " red skills for lifeStoneLevel " + i);
        }
    }

    // =========================================================
    // Nested Class

    public class augmentationSkill {
        private final int _skillId;
        private final int _skillLevel;

        public augmentationSkill(int skillId, int skillLevel) {
            _skillId = skillId;
            _skillLevel = skillLevel;
        }

        public L2Skill getSkill() {
            return SkillTable.getInstance().getInfo(_skillId, _skillLevel);
        }
    }

    public class augmentationStat {
        private final Stats _stat;
        private final int _singleSize;
        private final int _combinedSize;
        private final float _singleValues[];
        private final 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;
        }
    }

    @SuppressWarnings("unchecked")
    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 {
            int badAugmantData = 0;
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);
            factory.setIgnoringComments(true);

            File file = new File(Config.DATAPACK_ROOT + "/data/stats/augmentation/augmentation_skillmap.xml");
            if (!file.exists()) {
                _log.warn("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());
                            int skillLvL = 0;
                            String type = "blue";

                            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) {
                                _log.warn("Bad skillId in augmentation_skillmap.xml in the augmentationId:"
                                        + augmentationId);
                                badAugmantData++;
                                continue;
                            } else if (skillLvL == 0) {
                                _log.warn("Bad skillLevel in augmentation_skillmap.xml in the augmentationId:"
                                        + augmentationId);
                                badAugmantData++;
                                continue;
                            }
                            int k = (augmentationId - BLUE_START) / SKILLS_BLOCKSIZE;

                            if (type.equalsIgnoreCase("blue"))
                                ((ArrayList<Integer>) _blueSkills[k]).add(augmentationId);
                            else if (type.equalsIgnoreCase("purple"))
                                ((ArrayList<Integer>) _purpleSkills[k]).add(augmentationId);
                            else
                                ((ArrayList<Integer>) _redSkills[k]).add(augmentationId);

                            _allSkills.put(augmentationId, new augmentationSkill(skillId, skillLvL));
                        }
                    }
                }
            }
            if (badAugmantData != 0)
                _log.info("AugmentationData: " + badAugmantData + " bad skill(s) were skipped.");
        } catch (Exception e) {
            _log.error("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()) {
                    _log.warn("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());
                                        FastList<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;
                                        }
                                    }
                                }
                                // store this stat
                                ((ArrayList<augmentationStat>) _augStats[(i - 1)]).add(new augmentationStat(
                                        Stats.valueOfXml(statName), soloValues, combinedValues));
                            }
                        }
                    }
                }
            } catch (Exception e) {
                _log.error("Error parsing augmentation_stats" + i + ".xml.", e);
                return;
            }

            try {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                factory.setValidating(false);
                factory.setIgnoringComments(true);

                File file = new File(
                        Config.DATAPACK_ROOT + "/data/stats/augmentation/augmentation_jewel_stats" + i + ".xml");

                if (!file.exists()) {
                    _log.warn("The jewel 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());
                                        FastList<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;
                                        }
                                    }
                                }
                                // store this stat
                                ((ArrayList<augmentationStat>) _augAccStats[(i - 1)]).add(new augmentationStat(
                                        Stats.valueOfXml(statName), soloValues, combinedValues));
                            }
                        }
                    }
                }
            } catch (Exception e) {
                _log.error("Error parsing jewel augmentation_stats" + i + ".xml.", e);
                return;
            }
        }
    }

    /**
     * Generate a new random augmentation
     * @param item
     * @param lifeStoneLevel
     * @param lifeSoneGrade
     * @param bodyPart
     * @return L2Augmentation
     */
    public L2Augmentation generateRandomAugmentation(int lifeStoneLevel, int lifeStoneGrade, int bodyPart) {
        switch (bodyPart) {
        case L2Item.SLOT_LR_FINGER:
        case L2Item.SLOT_LR_EAR:
        case L2Item.SLOT_NECK:
            return generateRandomAccessoryAugmentation(lifeStoneLevel, bodyPart);
        default:
            return generateRandomWeaponAugmentation(lifeStoneLevel, lifeStoneGrade);
        }
    }

    private L2Augmentation generateRandomWeaponAugmentation(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 stat12 = 0;
        int stat34 = 0;
        boolean generateSkill = false;
        boolean generateGlow = false;

        //lifestonelevel is used for stat Id and skill level, but here the max level is 9
        lifeStoneLevel = Math.min(lifeStoneLevel, 9);

        switch (lifeStoneGrade) {
        case AbstractRefinePacket.GRADE_NONE:
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_NG_SKILL_CHANCE)
                generateSkill = true;
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_NG_GLOW_CHANCE)
                generateGlow = true;
            break;
        case AbstractRefinePacket.GRADE_MID:
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_MID_SKILL_CHANCE)
                generateSkill = true;
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_MID_GLOW_CHANCE)
                generateGlow = true;
            break;
        case AbstractRefinePacket.GRADE_HIGH:
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_HIGH_SKILL_CHANCE)
                generateSkill = true;
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_HIGH_GLOW_CHANCE)
                generateGlow = true;
            break;
        case AbstractRefinePacket.GRADE_TOP:
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_TOP_SKILL_CHANCE)
                generateSkill = true;
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_TOP_GLOW_CHANCE)
                generateGlow = true;
            break;
        case AbstractRefinePacket.GRADE_ACC:
            if (Rnd.get(1, 100) <= Config.AUGMENTATION_ACC_SKILL_CHANCE)
                generateSkill = true;
        }

        if (!generateSkill && 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: 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
        int resultColor = Rnd.get(0, 100);
        if (stat34 == 0 && !generateSkill) {
            if (resultColor <= (15 * lifeStoneGrade) + 40)
                resultColor = 1;
            else
                resultColor = 0;
        } else {
            if (resultColor <= (10 * lifeStoneGrade) + 5 || stat34 != 0)
                resultColor = 3;
            else if (resultColor <= (10 * lifeStoneGrade) + 10)
                resultColor = 1;
            else
                resultColor = 2;
        }

        // generate a skill if neccessary
        L2Skill skill = null;
        if (generateSkill) {
            switch (resultColor) {
            case 1: // blue skill
                stat34 = ((Integer) _blueSkills[lifeStoneLevel]
                        .get(Rnd.get(0, _blueSkills[lifeStoneLevel].size() - 1)));
                break;
            case 2: // purple skill
                stat34 = ((Integer) _purpleSkills[lifeStoneLevel]
                        .get(Rnd.get(0, _purpleSkills[lifeStoneLevel].size() - 1)));
                break;
            case 3: // red skill
                stat34 = ((Integer) _redSkills[lifeStoneLevel]
                        .get(Rnd.get(0, _redSkills[lifeStoneLevel].size() - 1)));
                break;
            }
            skill = _allSkills.get(stat34).getSkill();
        }

        // Third: Calculate the subblock offset for the choosen color,
        // and the level of the lifeStone
        // from large number of retail augmentations:
        // no skill part
        // Id for stat12:
        // A:1-910 B:911-1820 C:1821-2730 D:2731-3640 E:3641-4550 F:4551-5460 G:5461-6370 H:6371-7280
        // Id for stat34(this defines the color):
        // I:7281-8190(yellow) K:8191-9100(blue) L:10921-11830(yellow) M:11831-12740(blue)
        // you can combine I-K with A-D and L-M with E-H
        // using C-D or G-H Id you will get a glow effect
        // there seems no correlation in which grade use which Id except for the glowing restriction
        // skill part
        // Id for stat12:
        // same for no skill part
        // A same as E, B same as F, C same as G, D same as H
        // A - no glow, no grade LS
        // B - weak glow, mid grade LS?
        // C - glow, high grade LS?
        // D - strong glow, top grade LS?

        // is neither a skill nor basestat used for stat34? then generate a normal stat
        int offset;
        if (stat34 == 0) {
            int temp = Rnd.get(2, 3);
            int colorOffset = resultColor * (10 * STAT_SUBBLOCKSIZE) + temp * STAT_BLOCKSIZE + 1;
            offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + colorOffset;

            stat34 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);
            if (generateGlow && lifeStoneGrade >= 2)
                offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + (temp - 2) * STAT_BLOCKSIZE
                        + lifeStoneGrade * (10 * STAT_SUBBLOCKSIZE) + 1;
            else
                offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + (temp - 2) * STAT_BLOCKSIZE
                        + Rnd.get(0, 1) * (10 * STAT_SUBBLOCKSIZE) + 1;
        } else {
            if (!generateGlow)
                offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + Rnd.get(0, 1) * STAT_BLOCKSIZE + 1;
            else
                offset = (lifeStoneLevel * STAT_SUBBLOCKSIZE) + Rnd.get(0, 1) * STAT_BLOCKSIZE
                        + (lifeStoneGrade + resultColor) / 2 * (10 * STAT_SUBBLOCKSIZE) + 1;
        }
        stat12 = Rnd.get(offset, offset + STAT_SUBBLOCKSIZE - 1);

        if (_log.isDebugEnabled())
            _log.debug("Augmentation success: stat12=" + stat12 + "; stat34=" + stat34 + "; resultColor="
                    + resultColor + "; level=" + lifeStoneLevel + "; grade=" + lifeStoneGrade);
        return new L2Augmentation(((stat34 << 16) + stat12), skill);
    }

    private L2Augmentation generateRandomAccessoryAugmentation(int lifeStoneLevel, int bodyPart) {
        int stat12 = 0;
        int stat34 = 0;
        int base = 0;
        int skillsLength = 0;

        lifeStoneLevel = Math.min(lifeStoneLevel, 9);

        switch (bodyPart) {
        case L2Item.SLOT_LR_FINGER:
            base = ACC_RING_START + ACC_RING_BLOCKSIZE * lifeStoneLevel;
            skillsLength = ACC_RING_SKILLS;
            break;
        case L2Item.SLOT_LR_EAR:
            base = ACC_EAR_START + ACC_EAR_BLOCKSIZE * lifeStoneLevel;
            skillsLength = ACC_EAR_SKILLS;
            break;
        case L2Item.SLOT_NECK:
            base = ACC_NECK_START + ACC_NECK_BLOCKSIZE * lifeStoneLevel;
            skillsLength = ACC_NECK_SKILLS;
            break;
        default:
            return null;
        }

        int resultColor = Rnd.get(0, 3);
        L2Skill skill = null;

        // first augmentation (stats only)
        stat12 = Rnd.get(ACC_STAT_SUBBLOCKSIZE);

        if (Rnd.get(1, 100) <= Config.AUGMENTATION_ACC_SKILL_CHANCE) {
            // second augmentation (skill)
            stat34 = base + Rnd.get(skillsLength);
            if (_allSkills.containsKey(stat34))
                skill = _allSkills.get(stat34).getSkill();
        }

        if (skill == null) {
            // second augmentation (stats)
            // calculating any different from stat12 value inside sub-block
            // starting from next and wrapping over using remainder
            stat34 = (stat12 + 1 + Rnd.get(ACC_STAT_SUBBLOCKSIZE - 1)) % ACC_STAT_SUBBLOCKSIZE;
            // this is a stats - skipping skills
            stat34 = base + skillsLength + ACC_STAT_SUBBLOCKSIZE * resultColor + stat34;
        }

        // stat12 has stats only
        stat12 = base + skillsLength + ACC_STAT_SUBBLOCKSIZE * resultColor + stat12;

        if (_log.isDebugEnabled())
            _log.info("Accessory augmentation success: stat12=" + stat12 + "; stat34=" + stat34 + "; level="
                    + lifeStoneLevel);
        return new L2Augmentation(((stat34 << 16) + stat12), skill);
    }

    public class AugStat {
        private final Stats _stat;
        private final 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 divided 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++) {
            // weapon augmentation - stats
            if (stats[i] >= STAT_START && stats[i] <= STAT_END) {
                int base = stats[i] - STAT_START;
                int color = base / STAT_BLOCKSIZE; // 4 color blocks
                int subblock = base % STAT_BLOCKSIZE; // offset in color block
                int level = subblock / STAT_SUBBLOCKSIZE; // stat level (sub-block number)
                int stat = subblock % STAT_SUBBLOCKSIZE; // offset in sub-block - stat

                byte stat1 = STATS1_MAP[stat];
                byte stat2 = STATS2_MAP[stat];
                if (stat1 == stat2) // solo stat
                {
                    augmentationStat as = ((augmentationStat) _augStats[color].get(stat1));
                    temp.add(new AugStat(as.getStat(), as.getSingleStatValue(level)));
                } else
                // combined stat
                {
                    augmentationStat as = ((augmentationStat) _augStats[color].get(stat1));
                    temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(level)));
                    as = ((augmentationStat) _augStats[color].get(stat2));
                    temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(level)));
                }
            }
            // 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;
                }
            }
            // accessory augmentation
            // 3 areas for rings, earrings and necklaces
            // each area consist of 10 blocks (level)
            // each block has skills first (18 or 24 for necklaces)
            // and sub-block for stats next
            else if (stats[i] >= ACC_START && stats[i] <= ACC_END) {
                int base, level, subblock;

                if (stats[i] <= ACC_RING_END) // rings area
                {
                    base = stats[i] - ACC_RING_START; // calculate base offset
                    level = base / ACC_RING_BLOCKSIZE; // stat level (block number)
                    subblock = (base % ACC_RING_BLOCKSIZE) - ACC_RING_SKILLS; // skills first
                } else if (stats[i] <= ACC_EAR_END) //earrings area
                {
                    base = stats[i] - ACC_EAR_START;
                    level = base / ACC_EAR_BLOCKSIZE;
                    subblock = (base % ACC_EAR_BLOCKSIZE) - ACC_EAR_SKILLS;
                } else
                // necklaces
                {
                    base = stats[i] - ACC_NECK_START;
                    level = base / ACC_NECK_BLOCKSIZE;
                    subblock = (base % ACC_NECK_BLOCKSIZE) - ACC_NECK_SKILLS;
                }

                if (subblock >= 0) // stat, not skill
                {
                    int color = subblock / ACC_STAT_SUBBLOCKSIZE;
                    int stat = subblock % ACC_STAT_SUBBLOCKSIZE;
                    byte stat1 = ACC_STATS1_MAP[stat];
                    byte stat2 = ACC_STATS2_MAP[stat];
                    if (stat1 == stat2) // solo
                    {
                        augmentationStat as = ((augmentationStat) _augAccStats[color].get(stat1));
                        temp.add(new AugStat(as.getStat(), as.getSingleStatValue(level)));
                    } else
                    // combined
                    {
                        augmentationStat as = ((augmentationStat) _augAccStats[color].get(stat1));
                        temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(level)));
                        as = ((augmentationStat) _augAccStats[color].get(stat2));
                        temp.add(new AugStat(as.getStat(), as.getCombinedStatValue(level)));
                    }
                }
            }
        }

        return temp;
    }

    /**
     * Returns skill by augmentation Id or null if not valid or not found
     */
    public L2Skill getAugSkillById(int augmentationId) {
        final augmentationSkill temp = _allSkills.get(augmentationId);
        if (temp == null)
            return null;

        return temp.getSkill();
    }

    @SuppressWarnings("synthetic-access")
    private static class SingletonHolder {
        protected static final AugmentationData _instance = new AugmentationData();
    }
}