This commit is contained in:
@@ -0,0 +1,958 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.l2jmobius.Config;
|
||||
import com.l2jmobius.gameserver.data.xml.impl.OptionData;
|
||||
import com.l2jmobius.gameserver.model.L2Augmentation;
|
||||
import com.l2jmobius.gameserver.model.holders.SkillHolder;
|
||||
import com.l2jmobius.gameserver.model.items.L2Item;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.model.options.Options;
|
||||
import com.l2jmobius.gameserver.network.clientpackets.AbstractRefinePacket;
|
||||
import com.l2jmobius.util.Rnd;
|
||||
|
||||
/**
|
||||
* Loads augmentation bonuses and skills.
|
||||
* @author durgus, Gigiikun, Sandro, UnAfraid
|
||||
*/
|
||||
public class AugmentationData
|
||||
{
|
||||
// TODO(Zoey76): Implement using IXmlReader.
|
||||
private static final Logger LOGGER = Logger.getLogger(AugmentationData.class.getName());
|
||||
|
||||
// stats
|
||||
private static final int STAT_BLOCKSIZE = 3640;
|
||||
private static final int STAT_SUBBLOCKSIZE = 91;
|
||||
public static final int MIN_SKILL_ID = STAT_BLOCKSIZE * 4;
|
||||
|
||||
// skills
|
||||
private static final int BLUE_START = 14561;
|
||||
private static final int SKILLS_BLOCKSIZE = 178;
|
||||
|
||||
// basestats
|
||||
private static final int BASESTAT_STR = 16341;
|
||||
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_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 final List<List<Integer>> _blueSkills = new ArrayList<>();
|
||||
private final List<List<Integer>> _purpleSkills = new ArrayList<>();
|
||||
private final List<List<Integer>> _redSkills = new ArrayList<>();
|
||||
private final List<List<Integer>> _yellowSkills = new ArrayList<>();
|
||||
|
||||
private final List<AugmentationChance> _augmentationChances = new ArrayList<>();
|
||||
private final List<augmentationChanceAcc> _augmentationChancesAcc = new ArrayList<>();
|
||||
|
||||
private final Map<Integer, SkillHolder> _allSkills = new HashMap<>();
|
||||
|
||||
protected AugmentationData()
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
_blueSkills.add(new ArrayList<Integer>());
|
||||
_purpleSkills.add(new ArrayList<Integer>());
|
||||
_redSkills.add(new ArrayList<Integer>());
|
||||
_yellowSkills.add(new ArrayList<Integer>());
|
||||
}
|
||||
|
||||
load();
|
||||
if (!Config.RETAIL_LIKE_AUGMENTATION)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
LOGGER.info(getClass().getSimpleName() + ": Loaded: " + _blueSkills.get(i).size() + " blue, " + _purpleSkills.get(i).size() + " purple and " + _redSkills.get(i).size() + " red skills for lifeStoneLevel " + i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _augmentationChances.size() + " augmentations.");
|
||||
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _augmentationChancesAcc.size() + " accessory augmentations.");
|
||||
}
|
||||
}
|
||||
|
||||
public class AugmentationChance
|
||||
{
|
||||
private final String _WeaponType;
|
||||
private final int _StoneId;
|
||||
private final int _VariationId;
|
||||
private final int _CategoryChance;
|
||||
private final int _AugmentId;
|
||||
private final float _AugmentChance;
|
||||
|
||||
public AugmentationChance(String WeaponType, int StoneId, int VariationId, int CategoryChance, int AugmentId, float AugmentChance)
|
||||
{
|
||||
_WeaponType = WeaponType;
|
||||
_StoneId = StoneId;
|
||||
_VariationId = VariationId;
|
||||
_CategoryChance = CategoryChance;
|
||||
_AugmentId = AugmentId;
|
||||
_AugmentChance = AugmentChance;
|
||||
}
|
||||
|
||||
public String getWeaponType()
|
||||
{
|
||||
return _WeaponType;
|
||||
}
|
||||
|
||||
public int getStoneId()
|
||||
{
|
||||
return _StoneId;
|
||||
}
|
||||
|
||||
public int getVariationId()
|
||||
{
|
||||
return _VariationId;
|
||||
}
|
||||
|
||||
public int getCategoryChance()
|
||||
{
|
||||
return _CategoryChance;
|
||||
}
|
||||
|
||||
public int getAugmentId()
|
||||
{
|
||||
return _AugmentId;
|
||||
}
|
||||
|
||||
public float getAugmentChance()
|
||||
{
|
||||
return _AugmentChance;
|
||||
}
|
||||
}
|
||||
|
||||
public class augmentationChanceAcc
|
||||
{
|
||||
private final String _WeaponType;
|
||||
private final int _StoneId;
|
||||
private final int _VariationId;
|
||||
private final int _CategoryChance;
|
||||
private final int _AugmentId;
|
||||
private final float _AugmentChance;
|
||||
|
||||
public augmentationChanceAcc(String WeaponType, int StoneId, int VariationId, int CategoryChance, int AugmentId, float AugmentChance)
|
||||
{
|
||||
_WeaponType = WeaponType;
|
||||
_StoneId = StoneId;
|
||||
_VariationId = VariationId;
|
||||
_CategoryChance = CategoryChance;
|
||||
_AugmentId = AugmentId;
|
||||
_AugmentChance = AugmentChance;
|
||||
}
|
||||
|
||||
public String getWeaponType()
|
||||
{
|
||||
return _WeaponType;
|
||||
}
|
||||
|
||||
public int getStoneId()
|
||||
{
|
||||
return _StoneId;
|
||||
}
|
||||
|
||||
public int getVariationId()
|
||||
{
|
||||
return _VariationId;
|
||||
}
|
||||
|
||||
public int getCategoryChance()
|
||||
{
|
||||
return _CategoryChance;
|
||||
}
|
||||
|
||||
public int getAugmentId()
|
||||
{
|
||||
return _AugmentId;
|
||||
}
|
||||
|
||||
public float getAugmentChance()
|
||||
{
|
||||
return _AugmentChance;
|
||||
}
|
||||
}
|
||||
|
||||
private final void load()
|
||||
{
|
||||
// Load stats
|
||||
final DocumentBuilderFactory factory2 = DocumentBuilderFactory.newInstance();
|
||||
factory2.setValidating(false);
|
||||
factory2.setIgnoringComments(true);
|
||||
|
||||
// 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...
|
||||
if (!Config.RETAIL_LIKE_AUGMENTATION)
|
||||
{
|
||||
try
|
||||
{
|
||||
int badAugmantData = 0;
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setValidating(false);
|
||||
factory.setIgnoringComments(true);
|
||||
|
||||
final File file = new File(Config.DATAPACK_ROOT + "/stats/augmentation/augmentation_skillmap.xml");
|
||||
if (!file.exists())
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR The augmentation skillmap file is missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
final 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;
|
||||
final int 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) || (skillLvL == 0))
|
||||
{
|
||||
badAugmantData++;
|
||||
continue;
|
||||
}
|
||||
final int k = (augmentationId - BLUE_START) / SKILLS_BLOCKSIZE;
|
||||
|
||||
if (type.equalsIgnoreCase("blue"))
|
||||
{
|
||||
_blueSkills.get(k).add(augmentationId);
|
||||
}
|
||||
else if (type.equalsIgnoreCase("purple"))
|
||||
{
|
||||
_purpleSkills.get(k).add(augmentationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_redSkills.get(k).add(augmentationId);
|
||||
}
|
||||
|
||||
_allSkills.put(augmentationId, new SkillHolder(skillId, skillLvL));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (badAugmantData != 0)
|
||||
{
|
||||
LOGGER.info(getClass().getSimpleName() + ": " + badAugmantData + " bad skill(s) were skipped.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR parsing augmentation_skillmap.xml.", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setValidating(false);
|
||||
factory.setIgnoringComments(true);
|
||||
final File aFile = new File(Config.DATAPACK_ROOT + "/stats/augmentation/retailchances.xml");
|
||||
if (!aFile.exists())
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR The retailchances.xml data file is missing.");
|
||||
return;
|
||||
}
|
||||
Document aDoc = null;
|
||||
try
|
||||
{
|
||||
aDoc = factory.newDocumentBuilder().parse(aFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
String aWeaponType = null;
|
||||
int aStoneId = 0;
|
||||
int aVariationId = 0;
|
||||
int aCategoryChance = 0;
|
||||
int aAugmentId = 0;
|
||||
float aAugmentChance = 0;
|
||||
|
||||
for (Node l = aDoc.getFirstChild(); l != null; l = l.getNextSibling())
|
||||
{
|
||||
if (l.getNodeName().equals("list"))
|
||||
{
|
||||
NamedNodeMap aNodeAttributes = null;
|
||||
|
||||
for (Node n = l.getFirstChild(); n != null; n = n.getNextSibling())
|
||||
{
|
||||
if (n.getNodeName().equals("weapon"))
|
||||
{
|
||||
aNodeAttributes = n.getAttributes();
|
||||
|
||||
aWeaponType = aNodeAttributes.getNamedItem("type").getNodeValue();
|
||||
|
||||
for (Node c = n.getFirstChild(); c != null; c = c.getNextSibling())
|
||||
{
|
||||
if (c.getNodeName().equals("stone"))
|
||||
{
|
||||
aNodeAttributes = c.getAttributes();
|
||||
|
||||
aStoneId = Integer.parseInt(aNodeAttributes.getNamedItem("id").getNodeValue());
|
||||
|
||||
for (Node v = c.getFirstChild(); v != null; v = v.getNextSibling())
|
||||
{
|
||||
if (v.getNodeName().equals("variation"))
|
||||
{
|
||||
aNodeAttributes = v.getAttributes();
|
||||
|
||||
aVariationId = Integer.parseInt(aNodeAttributes.getNamedItem("id").getNodeValue());
|
||||
|
||||
for (Node j = v.getFirstChild(); j != null; j = j.getNextSibling())
|
||||
{
|
||||
if (j.getNodeName().equals("category"))
|
||||
{
|
||||
aNodeAttributes = j.getAttributes();
|
||||
|
||||
aCategoryChance = Integer.parseInt(aNodeAttributes.getNamedItem("probability").getNodeValue());
|
||||
|
||||
for (Node e = j.getFirstChild(); e != null; e = e.getNextSibling())
|
||||
{
|
||||
if (e.getNodeName().equals("augment"))
|
||||
{
|
||||
aNodeAttributes = e.getAttributes();
|
||||
|
||||
aAugmentId = Integer.parseInt(aNodeAttributes.getNamedItem("id").getNodeValue());
|
||||
aAugmentChance = Float.parseFloat(aNodeAttributes.getNamedItem("chance").getNodeValue());
|
||||
|
||||
_augmentationChances.add(new AugmentationChance(aWeaponType, aStoneId, aVariationId, aCategoryChance, aAugmentId, aAugmentChance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Config.RETAIL_LIKE_AUGMENTATION_ACCESSORY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
final DocumentBuilderFactory factory3 = DocumentBuilderFactory.newInstance();
|
||||
factory3.setValidating(false);
|
||||
factory3.setIgnoringComments(true);
|
||||
final File aFile3 = new File(Config.DATAPACK_ROOT + "/stats/augmentation/retailchances_accessory.xml");
|
||||
if (!aFile3.exists())
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": ERROR The retailchances_accessory.xml data file is missing.");
|
||||
return;
|
||||
}
|
||||
Document aDoc = null;
|
||||
try
|
||||
{
|
||||
aDoc = factory3.newDocumentBuilder().parse(aFile3);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
String aWeaponType = null;
|
||||
int aStoneId = 0;
|
||||
int aVariationId = 0;
|
||||
int aCategoryChance = 0;
|
||||
int aAugmentId = 0;
|
||||
float aAugmentChance = 0;
|
||||
|
||||
for (Node l = aDoc.getFirstChild(); l != null; l = l.getNextSibling())
|
||||
{
|
||||
if (l.getNodeName().equals("list"))
|
||||
{
|
||||
NamedNodeMap aNodeAttributes = null;
|
||||
for (Node n = l.getFirstChild(); n != null; n = n.getNextSibling())
|
||||
{
|
||||
if (n.getNodeName().equals("weapon"))
|
||||
{
|
||||
aNodeAttributes = n.getAttributes();
|
||||
|
||||
aWeaponType = aNodeAttributes.getNamedItem("type").getNodeValue();
|
||||
|
||||
for (Node c = n.getFirstChild(); c != null; c = c.getNextSibling())
|
||||
{
|
||||
if (c.getNodeName().equals("stone"))
|
||||
{
|
||||
aNodeAttributes = c.getAttributes();
|
||||
|
||||
aStoneId = Integer.parseInt(aNodeAttributes.getNamedItem("id").getNodeValue());
|
||||
|
||||
for (Node v = c.getFirstChild(); v != null; v = v.getNextSibling())
|
||||
{
|
||||
if (v.getNodeName().equals("variation"))
|
||||
{
|
||||
aNodeAttributes = v.getAttributes();
|
||||
|
||||
aVariationId = Integer.parseInt(aNodeAttributes.getNamedItem("id").getNodeValue());
|
||||
|
||||
for (Node j = v.getFirstChild(); j != null; j = j.getNextSibling())
|
||||
{
|
||||
if (j.getNodeName().equals("category"))
|
||||
{
|
||||
aNodeAttributes = j.getAttributes();
|
||||
|
||||
aCategoryChance = Integer.parseInt(aNodeAttributes.getNamedItem("probability").getNodeValue());
|
||||
|
||||
for (Node e = j.getFirstChild(); e != null; e = e.getNextSibling())
|
||||
{
|
||||
if (e.getNodeName().equals("augment"))
|
||||
{
|
||||
aNodeAttributes = e.getAttributes();
|
||||
|
||||
aAugmentId = Integer.parseInt(aNodeAttributes.getNamedItem("id").getNodeValue());
|
||||
aAugmentChance = Float.parseFloat(aNodeAttributes.getNamedItem("chance").getNodeValue());
|
||||
|
||||
_augmentationChancesAcc.add(new augmentationChanceAcc(aWeaponType, aStoneId, aVariationId, aCategoryChance, aAugmentId, aAugmentChance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new random augmentation
|
||||
* @param lifeStoneLevel
|
||||
* @param lifeStoneGrade
|
||||
* @param bodyPart
|
||||
* @param lifeStoneId
|
||||
* @param targetItem
|
||||
* @return
|
||||
*/
|
||||
public L2Augmentation generateRandomAugmentation(int lifeStoneLevel, int lifeStoneGrade, int bodyPart, int lifeStoneId, L2ItemInstance targetItem)
|
||||
{
|
||||
switch (bodyPart)
|
||||
{
|
||||
case L2Item.SLOT_LR_FINGER:
|
||||
case L2Item.SLOT_LR_EAR:
|
||||
case L2Item.SLOT_NECK:
|
||||
{
|
||||
return generateRandomAccessoryAugmentation(lifeStoneLevel, bodyPart, lifeStoneId);
|
||||
}
|
||||
default:
|
||||
{
|
||||
return generateRandomWeaponAugmentation(lifeStoneLevel, lifeStoneGrade, lifeStoneId, targetItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private L2Augmentation generateRandomWeaponAugmentation(int lifeStoneLevel, int lifeStoneGrade, int lifeStoneId, L2ItemInstance item)
|
||||
{
|
||||
int stat12 = 0;
|
||||
int stat34 = 0;
|
||||
if (Config.RETAIL_LIKE_AUGMENTATION)
|
||||
{
|
||||
final List<AugmentationChance> _selectedChances12 = new ArrayList<>();
|
||||
final List<AugmentationChance> _selectedChances34 = new ArrayList<>();
|
||||
if (item.getItem().isMagicWeapon())
|
||||
{
|
||||
for (AugmentationChance ac : _augmentationChances)
|
||||
{
|
||||
if (ac.getWeaponType().equals("mage") && (ac.getStoneId() == lifeStoneId))
|
||||
{
|
||||
if (ac.getVariationId() == 1)
|
||||
{
|
||||
_selectedChances12.add(ac);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedChances34.add(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (AugmentationChance ac : _augmentationChances)
|
||||
{
|
||||
if (ac.getWeaponType().equals("warrior") && (ac.getStoneId() == lifeStoneId))
|
||||
{
|
||||
if (ac.getVariationId() == 1)
|
||||
{
|
||||
_selectedChances12.add(ac);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedChances34.add(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
int r = Rnd.get(10000);
|
||||
float s = 10000;
|
||||
for (AugmentationChance ac : _selectedChances12)
|
||||
{
|
||||
if (s > r)
|
||||
{
|
||||
s -= ac.getAugmentChance() * 100;
|
||||
stat12 = ac.getAugmentId();
|
||||
}
|
||||
}
|
||||
int[] gradeChance = null;
|
||||
switch (lifeStoneGrade)
|
||||
{
|
||||
case AbstractRefinePacket.GRADE_NONE:
|
||||
{
|
||||
gradeChance = Config.RETAIL_LIKE_AUGMENTATION_NG_CHANCE;
|
||||
break;
|
||||
}
|
||||
case AbstractRefinePacket.GRADE_MID:
|
||||
{
|
||||
gradeChance = Config.RETAIL_LIKE_AUGMENTATION_MID_CHANCE;
|
||||
break;
|
||||
}
|
||||
case AbstractRefinePacket.GRADE_HIGH:
|
||||
{
|
||||
gradeChance = Config.RETAIL_LIKE_AUGMENTATION_HIGH_CHANCE;
|
||||
break;
|
||||
}
|
||||
case AbstractRefinePacket.GRADE_TOP:
|
||||
{
|
||||
gradeChance = Config.RETAIL_LIKE_AUGMENTATION_TOP_CHANCE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
gradeChance = Config.RETAIL_LIKE_AUGMENTATION_NG_CHANCE;
|
||||
}
|
||||
}
|
||||
int c = Rnd.get(100);
|
||||
if (c < gradeChance[0])
|
||||
{
|
||||
c = 55;
|
||||
}
|
||||
else if (c < (gradeChance[0] + gradeChance[1]))
|
||||
{
|
||||
c = 35;
|
||||
}
|
||||
else if (c < (gradeChance[0] + gradeChance[1] + gradeChance[2]))
|
||||
{
|
||||
c = 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = 3;
|
||||
}
|
||||
final List<AugmentationChance> _selectedChances34final = new ArrayList<>();
|
||||
for (AugmentationChance ac : _selectedChances34)
|
||||
{
|
||||
if (ac.getCategoryChance() == c)
|
||||
{
|
||||
_selectedChances34final.add(ac);
|
||||
}
|
||||
}
|
||||
r = Rnd.get(10000);
|
||||
s = 10000;
|
||||
for (AugmentationChance ac : _selectedChances34final)
|
||||
{
|
||||
if (s > r)
|
||||
{
|
||||
s -= ac.getAugmentChance() * 100;
|
||||
stat34 = ac.getAugmentId();
|
||||
}
|
||||
}
|
||||
return new L2Augmentation(((stat34 << 16) + stat12));
|
||||
}
|
||||
boolean generateSkill = false;
|
||||
boolean generateGlow = false;
|
||||
|
||||
// life stone level 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,
|
||||
// what's known is: you can't 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 necessary
|
||||
if (generateSkill)
|
||||
{
|
||||
switch (resultColor)
|
||||
{
|
||||
case 1: // blue skill
|
||||
{
|
||||
stat34 = _blueSkills.get(lifeStoneLevel).get(Rnd.get(0, _blueSkills.get(lifeStoneLevel).size() - 1));
|
||||
break;
|
||||
}
|
||||
case 2: // purple skill
|
||||
{
|
||||
stat34 = _purpleSkills.get(lifeStoneLevel).get(Rnd.get(0, _purpleSkills.get(lifeStoneLevel).size() - 1));
|
||||
break;
|
||||
}
|
||||
case 3: // red skill
|
||||
{
|
||||
stat34 = _redSkills.get(lifeStoneLevel).get(Rnd.get(0, _redSkills.get(lifeStoneLevel).size() - 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third: Calculate the subblock offset for the chosen 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)
|
||||
{
|
||||
final int temp = Rnd.get(2, 3);
|
||||
final 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 (Config.DEBUG)
|
||||
{
|
||||
LOGGER.info(getClass().getSimpleName() + ": Augmentation success: stat12=" + stat12 + "; stat34=" + stat34 + "; resultColor=" + resultColor + "; level=" + lifeStoneLevel + "; grade=" + lifeStoneGrade);
|
||||
}
|
||||
return new L2Augmentation(((stat34 << 16) + stat12));
|
||||
}
|
||||
|
||||
private L2Augmentation generateRandomAccessoryAugmentation(int lifeStoneLevel, int bodyPart, int lifeStoneId)
|
||||
{
|
||||
int stat12 = 0;
|
||||
int stat34 = 0;
|
||||
if (Config.RETAIL_LIKE_AUGMENTATION_ACCESSORY)
|
||||
{
|
||||
final List<augmentationChanceAcc> _selectedChances12 = new ArrayList<>();
|
||||
final List<augmentationChanceAcc> _selectedChances34 = new ArrayList<>();
|
||||
for (augmentationChanceAcc ac : _augmentationChancesAcc)
|
||||
{
|
||||
if (ac.getWeaponType().equals("warrior") && (ac.getStoneId() == lifeStoneId))
|
||||
{
|
||||
if (ac.getVariationId() == 1)
|
||||
{
|
||||
_selectedChances12.add(ac);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedChances34.add(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
int r = Rnd.get(10000);
|
||||
float s = 10000;
|
||||
for (augmentationChanceAcc ac : _selectedChances12)
|
||||
{
|
||||
if (s > r)
|
||||
{
|
||||
s -= ac.getAugmentChance() * 100;
|
||||
stat12 = ac.getAugmentId();
|
||||
}
|
||||
}
|
||||
int c = Rnd.get(100);
|
||||
if (c < 55)
|
||||
{
|
||||
c = 55;
|
||||
}
|
||||
else if (c < 90)
|
||||
{
|
||||
c = 35;
|
||||
}
|
||||
else if (c < 99)
|
||||
{
|
||||
c = 9;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = 1;
|
||||
}
|
||||
final List<augmentationChanceAcc> _selectedChances34final = new ArrayList<>();
|
||||
for (augmentationChanceAcc ac : _selectedChances34)
|
||||
{
|
||||
if (ac.getCategoryChance() == c)
|
||||
{
|
||||
_selectedChances34final.add(ac);
|
||||
}
|
||||
}
|
||||
r = Rnd.get(10000);
|
||||
s = 10000;
|
||||
for (augmentationChanceAcc ac : _selectedChances34final)
|
||||
{
|
||||
if (s > r)
|
||||
{
|
||||
s -= ac.getAugmentChance() * 100;
|
||||
stat34 = ac.getAugmentId();
|
||||
}
|
||||
}
|
||||
|
||||
return new L2Augmentation(((stat34 << 16) + stat12));
|
||||
}
|
||||
lifeStoneLevel = Math.min(lifeStoneLevel, 9);
|
||||
int base = 0;
|
||||
int skillsLength = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
final int resultColor = Rnd.get(0, 3);
|
||||
|
||||
// first augmentation (stats only)
|
||||
stat12 = Rnd.get(ACC_STAT_SUBBLOCKSIZE);
|
||||
Options op = null;
|
||||
if (Rnd.get(1, 100) <= Config.AUGMENTATION_ACC_SKILL_CHANCE)
|
||||
{
|
||||
// second augmentation (skill)
|
||||
stat34 = base + Rnd.get(skillsLength);
|
||||
op = OptionData.getInstance().getOptions(stat34);
|
||||
}
|
||||
|
||||
if ((op == null) || (!op.hasActiveSkill() && !op.hasPassiveSkill() && !op.hasActivationSkills()))
|
||||
{
|
||||
// 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 (Config.DEBUG)
|
||||
{
|
||||
LOGGER.info(getClass().getSimpleName() + ": Accessory augmentation success: stat12=" + stat12 + "; stat34=" + stat34 + "; level=" + lifeStoneLevel);
|
||||
}
|
||||
return new L2Augmentation(((stat34 << 16) + stat12));
|
||||
}
|
||||
|
||||
public static AugmentationData getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final AugmentationData _instance = new AugmentationData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,617 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import com.l2jmobius.Config;
|
||||
import com.l2jmobius.commons.database.DatabaseFactory;
|
||||
import com.l2jmobius.gameserver.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.model.L2Clan;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
import com.l2jmobius.gameserver.model.zone.ZoneId;
|
||||
import com.l2jmobius.gameserver.network.SystemMessageId;
|
||||
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
|
||||
|
||||
/**
|
||||
* @author BiggBoss
|
||||
*/
|
||||
public final class BotReportTable
|
||||
{
|
||||
// Zoey76: TODO: Split XML parsing from SQL operations, use IXmlReader instead of SAXParser.
|
||||
private static final Logger LOGGER = Logger.getLogger(BotReportTable.class.getName());
|
||||
|
||||
private static final int COLUMN_BOT_ID = 1;
|
||||
private static final int COLUMN_REPORTER_ID = 2;
|
||||
private static final int COLUMN_REPORT_TIME = 3;
|
||||
|
||||
public static final int ATTACK_ACTION_BLOCK_ID = -1;
|
||||
public static final int TRADE_ACTION_BLOCK_ID = -2;
|
||||
public static final int PARTY_ACTION_BLOCK_ID = -3;
|
||||
public static final int ACTION_BLOCK_ID = -4;
|
||||
public static final int CHAT_BLOCK_ID = -5;
|
||||
|
||||
private static final String SQL_LOAD_REPORTED_CHAR_DATA = "SELECT * FROM bot_reported_char_data";
|
||||
private static final String SQL_INSERT_REPORTED_CHAR_DATA = "INSERT INTO bot_reported_char_data VALUES (?,?,?)";
|
||||
private static final String SQL_CLEAR_REPORTED_CHAR_DATA = "DELETE FROM bot_reported_char_data";
|
||||
|
||||
private Map<Integer, Long> _ipRegistry;
|
||||
private Map<Integer, ReporterCharData> _charRegistry;
|
||||
private Map<Integer, ReportedCharData> _reports;
|
||||
private Map<Integer, PunishHolder> _punishments;
|
||||
|
||||
BotReportTable()
|
||||
{
|
||||
if (!Config.BOTREPORT_ENABLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ipRegistry = new HashMap<>();
|
||||
_charRegistry = new ConcurrentHashMap<>();
|
||||
_reports = new ConcurrentHashMap<>();
|
||||
_punishments = new ConcurrentHashMap<>();
|
||||
|
||||
try
|
||||
{
|
||||
final File punishments = new File("./config/BotReportPunishments.xml");
|
||||
if (!punishments.exists())
|
||||
{
|
||||
throw new FileNotFoundException(punishments.getName());
|
||||
}
|
||||
|
||||
SAXParserFactory.newInstance().newSAXParser().parse(punishments, new PunishmentsLoader());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, "BotReportTable: Could not load punishments from /config/BotReportPunishments.xml", e);
|
||||
}
|
||||
|
||||
loadReportedCharData();
|
||||
scheduleResetPointTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all reports of each reported bot into this cache class.<br>
|
||||
* Warning: Heavy method, used only on server start up
|
||||
*/
|
||||
private void loadReportedCharData()
|
||||
{
|
||||
try (Connection con = DatabaseFactory.getInstance().getConnection();
|
||||
Statement st = con.createStatement();
|
||||
ResultSet rset = st.executeQuery(SQL_LOAD_REPORTED_CHAR_DATA))
|
||||
{
|
||||
long lastResetTime = 0;
|
||||
try
|
||||
{
|
||||
final String[] hour = Config.BOTREPORT_RESETPOINT_HOUR;
|
||||
final Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour[0]));
|
||||
c.set(Calendar.MINUTE, Integer.parseInt(hour[1]));
|
||||
|
||||
if (System.currentTimeMillis() < c.getTimeInMillis())
|
||||
{
|
||||
c.set(Calendar.DAY_OF_YEAR, c.get(Calendar.DAY_OF_YEAR) - 1);
|
||||
}
|
||||
|
||||
lastResetTime = c.getTimeInMillis();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
|
||||
while (rset.next())
|
||||
{
|
||||
final int botId = rset.getInt(COLUMN_BOT_ID);
|
||||
final int reporter = rset.getInt(COLUMN_REPORTER_ID);
|
||||
final long date = rset.getLong(COLUMN_REPORT_TIME);
|
||||
if (_reports.containsKey(botId))
|
||||
{
|
||||
_reports.get(botId).addReporter(reporter, date);
|
||||
}
|
||||
else
|
||||
{
|
||||
final ReportedCharData rcd = new ReportedCharData();
|
||||
rcd.addReporter(reporter, date);
|
||||
_reports.put(rset.getInt(COLUMN_BOT_ID), rcd);
|
||||
}
|
||||
|
||||
if (date > lastResetTime)
|
||||
{
|
||||
ReporterCharData rcd = _charRegistry.get(reporter);
|
||||
if (rcd != null)
|
||||
{
|
||||
rcd.setPoints(rcd.getPointsLeft() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rcd = new ReporterCharData();
|
||||
rcd.setPoints(6);
|
||||
_charRegistry.put(reporter, rcd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.info("BotReportTable: Loaded " + _reports.size() + " bot reports");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, "BotReportTable: Could not load reported char data!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all reports for each reported bot down to database.<br>
|
||||
* Warning: Heavy method, used only at server shutdown
|
||||
*/
|
||||
public void saveReportedCharData()
|
||||
{
|
||||
try (Connection con = DatabaseFactory.getInstance().getConnection();
|
||||
Statement st = con.createStatement();
|
||||
PreparedStatement ps = con.prepareStatement(SQL_INSERT_REPORTED_CHAR_DATA))
|
||||
{
|
||||
st.execute(SQL_CLEAR_REPORTED_CHAR_DATA);
|
||||
|
||||
for (Map.Entry<Integer, ReportedCharData> entrySet : _reports.entrySet())
|
||||
{
|
||||
for (int reporterId : entrySet.getValue()._reporters.keySet())
|
||||
{
|
||||
ps.setInt(1, entrySet.getKey());
|
||||
ps.setInt(2, reporterId);
|
||||
ps.setLong(3, entrySet.getValue()._reporters.get(reporterId));
|
||||
ps.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.SEVERE, "BotReportTable: Could not update reported char data in database!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to perform a bot report. R/W to ip and char id registry is synchronized. Triggers bot punish management<br>
|
||||
* @param reporter (L2PcInstance who issued the report)
|
||||
* @return True, if the report was registered, False otherwise
|
||||
*/
|
||||
public boolean reportBot(L2PcInstance reporter)
|
||||
{
|
||||
final L2Object target = reporter.getTarget();
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final L2PcInstance bot = target.getActingPlayer();
|
||||
|
||||
if ((bot == null) || (target.getObjectId() == reporter.getObjectId()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot.isInsideZone(ZoneId.PEACE) || bot.isInsideZone(ZoneId.PVP))
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_A_CHARACTER_WHO_IS_IN_A_PEACE_ZONE_OR_A_BATTLEGROUND);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot.isInOlympiadMode())
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.THIS_CHARACTER_CANNOT_MAKE_A_REPORT_YOU_CANNOT_MAKE_A_REPORT_WHILE_LOCATED_INSIDE_A_PEACE_ZONE_OR_A_BATTLEGROUND_WHILE_YOU_ARE_AN_OPPOSING_CLAN_MEMBER_DURING_A_CLAN_WAR_OR_WHILE_PARTICIPATING_IN_THE_OLYMPIAD);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((bot.getClan() != null) && bot.getClan().isAtWarWith(reporter.getClan()))
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_WHEN_A_CLAN_WAR_HAS_BEEN_DECLARED);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bot.getExp() == bot.getStat().getStartingExp())
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_A_CHARACTER_WHO_HAS_NOT_ACQUIRED_ANY_XP_AFTER_CONNECTING);
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportedCharData rcd = _reports.get(bot.getObjectId());
|
||||
ReporterCharData rcdRep = _charRegistry.get(reporter.getObjectId());
|
||||
final int reporterId = reporter.getObjectId();
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
if (_reports.containsKey(reporterId))
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.YOU_HAVE_BEEN_REPORTED_AS_AN_ILLEGAL_PROGRAM_USER_AND_CANNOT_REPORT_OTHER_USERS);
|
||||
return false;
|
||||
}
|
||||
|
||||
final int ip = hashIp(reporter);
|
||||
if (!timeHasPassed(_ipRegistry, ip))
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.THIS_CHARACTER_CANNOT_MAKE_A_REPORT_THE_TARGET_HAS_ALREADY_BEEN_REPORTED_BY_EITHER_YOUR_CLAN_OR_ALLIANCE_OR_HAS_ALREADY_BEEN_REPORTED_FROM_YOUR_CURRENT_IP);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rcd != null)
|
||||
{
|
||||
if (rcd.alredyReportedBy(reporterId))
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.YOU_CANNOT_REPORT_THIS_PERSON_AGAIN_AT_THIS_TIME);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Config.BOTREPORT_ALLOW_REPORTS_FROM_SAME_CLAN_MEMBERS && rcd.reportedBySameClan(reporter.getClan()))
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.THIS_CHARACTER_CANNOT_MAKE_A_REPORT_THE_TARGET_HAS_ALREADY_BEEN_REPORTED_BY_EITHER_YOUR_CLAN_OR_ALLIANCE_OR_HAS_ALREADY_BEEN_REPORTED_FROM_YOUR_CURRENT_IP);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rcdRep != null)
|
||||
{
|
||||
if (rcdRep.getPointsLeft() == 0)
|
||||
{
|
||||
reporter.sendPacket(SystemMessageId.YOU_HAVE_USED_ALL_AVAILABLE_POINTS_POINTS_ARE_RESET_EVERYDAY_AT_NOON);
|
||||
return false;
|
||||
}
|
||||
|
||||
final long reuse = System.currentTimeMillis() - rcdRep.getLastReporTime();
|
||||
if (reuse < Config.BOTREPORT_REPORT_DELAY)
|
||||
{
|
||||
final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_CAN_MAKE_ANOTHER_REPORT_IN_S1_MINUTE_S_YOU_HAVE_S2_POINT_S_REMAINING_ON_THIS_ACCOUNT);
|
||||
sm.addInt((int) (reuse / 60000));
|
||||
sm.addInt(rcdRep.getPointsLeft());
|
||||
reporter.sendPacket(sm);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final long curTime = System.currentTimeMillis();
|
||||
|
||||
if (rcd == null)
|
||||
{
|
||||
rcd = new ReportedCharData();
|
||||
_reports.put(bot.getObjectId(), rcd);
|
||||
}
|
||||
rcd.addReporter(reporterId, curTime);
|
||||
|
||||
if (rcdRep == null)
|
||||
{
|
||||
rcdRep = new ReporterCharData();
|
||||
}
|
||||
rcdRep.registerReport(curTime);
|
||||
|
||||
_ipRegistry.put(ip, curTime);
|
||||
_charRegistry.put(reporterId, rcdRep);
|
||||
}
|
||||
|
||||
SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_WAS_REPORTED_AS_A_BOT);
|
||||
sm.addCharName(bot);
|
||||
reporter.sendPacket(sm);
|
||||
|
||||
sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_USED_A_REPORT_POINT_ON_C1_YOU_HAVE_S2_POINTS_REMAINING_ON_THIS_ACCOUNT);
|
||||
sm.addCharName(bot);
|
||||
sm.addInt(rcdRep.getPointsLeft());
|
||||
reporter.sendPacket(sm);
|
||||
|
||||
handleReport(bot, rcd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the punishs to apply to the given bot and triggers the punish method.
|
||||
* @param bot (L2PcInstance to be punished)
|
||||
* @param rcd (RepotedCharData linked to this bot)
|
||||
*/
|
||||
private void handleReport(L2PcInstance bot, ReportedCharData rcd)
|
||||
{
|
||||
// Report count punishment
|
||||
punishBot(bot, _punishments.get(rcd.getReportCount()));
|
||||
|
||||
// Range punishments
|
||||
for (int key : _punishments.keySet())
|
||||
{
|
||||
if ((key < 0) && (Math.abs(key) <= rcd.getReportCount()))
|
||||
{
|
||||
punishBot(bot, _punishments.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given punish to the bot if the action is secure
|
||||
* @param bot (L2PcInstance to punish)
|
||||
* @param ph (PunishHolder containing the debuff and a possible system message to send)
|
||||
*/
|
||||
private void punishBot(L2PcInstance bot, PunishHolder ph)
|
||||
{
|
||||
if (ph == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ph._punish.applyEffects(bot, bot);
|
||||
if (ph._systemMessageId <= -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
final SystemMessageId id = SystemMessageId.getSystemMessageId(ph._systemMessageId);
|
||||
if (id != null)
|
||||
{
|
||||
bot.sendPacket(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a debuff punishment into the punishments record. If skill does not exist, will log it and return
|
||||
* @param neededReports (report count to trigger this debuff)
|
||||
* @param skillId
|
||||
* @param skillLevel
|
||||
* @param sysMsg (id of a system message to send when applying the punish)
|
||||
*/
|
||||
void addPunishment(int neededReports, int skillId, int skillLevel, int sysMsg)
|
||||
{
|
||||
final Skill sk = SkillData.getInstance().getSkill(skillId, skillLevel);
|
||||
if (sk != null)
|
||||
{
|
||||
_punishments.put(neededReports, new PunishHolder(sk, sysMsg));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.warning("BotReportTable: Could not add punishment for " + neededReports + " report(s): Skill " + skillId + "-" + skillLevel + " does not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
void resetPointsAndSchedule()
|
||||
{
|
||||
synchronized (_charRegistry)
|
||||
{
|
||||
for (ReporterCharData rcd : _charRegistry.values())
|
||||
{
|
||||
rcd.setPoints(7);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleResetPointTask();
|
||||
}
|
||||
|
||||
private void scheduleResetPointTask()
|
||||
{
|
||||
try
|
||||
{
|
||||
final String[] hour = Config.BOTREPORT_RESETPOINT_HOUR;
|
||||
final Calendar c = Calendar.getInstance();
|
||||
c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour[0]));
|
||||
c.set(Calendar.MINUTE, Integer.parseInt(hour[1]));
|
||||
|
||||
if (System.currentTimeMillis() > c.getTimeInMillis())
|
||||
{
|
||||
c.set(Calendar.DAY_OF_YEAR, c.get(Calendar.DAY_OF_YEAR) + 1);
|
||||
}
|
||||
|
||||
ThreadPoolManager.getInstance().scheduleGeneral(new ResetPointTask(), c.getTimeInMillis() - System.currentTimeMillis());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ThreadPoolManager.getInstance().scheduleGeneral(new ResetPointTask(), 24 * 3600 * 1000);
|
||||
LOGGER.log(Level.WARNING, "BotReportTable: Could not properly schedule bot report points reset task. Scheduled in 24 hours.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static BotReportTable getInstance()
|
||||
{
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a integer representative number from a connection
|
||||
* @param player (The L2PcInstance owner of the connection)
|
||||
* @return int (hashed ip)
|
||||
*/
|
||||
private static int hashIp(L2PcInstance player)
|
||||
{
|
||||
final String con = player.getClient().getConnection().getInetAddress().getHostAddress();
|
||||
final String[] rawByte = con.split("\\.");
|
||||
final int[] rawIp = new int[4];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
rawIp[i] = Integer.parseInt(rawByte[i]);
|
||||
}
|
||||
|
||||
return rawIp[0] | (rawIp[1] << 8) | (rawIp[2] << 16) | (rawIp[3] << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and return if the abstrat barrier specified by an integer (map key) has accomplished the waiting time
|
||||
* @param map (a Map to study (Int = barrier, Long = fully qualified unix time)
|
||||
* @param objectId (an existent map key)
|
||||
* @return true if the time has passed.
|
||||
*/
|
||||
private static boolean timeHasPassed(Map<Integer, Long> map, int objectId)
|
||||
{
|
||||
return !map.containsKey(objectId) || ((System.currentTimeMillis() - map.get(objectId)) > Config.BOTREPORT_REPORT_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the info about a reporter
|
||||
*/
|
||||
private final class ReporterCharData
|
||||
{
|
||||
private long _lastReport;
|
||||
private byte _reportPoints;
|
||||
|
||||
ReporterCharData()
|
||||
{
|
||||
_reportPoints = 7;
|
||||
_lastReport = 0;
|
||||
}
|
||||
|
||||
void registerReport(long time)
|
||||
{
|
||||
_reportPoints -= 1;
|
||||
_lastReport = time;
|
||||
}
|
||||
|
||||
long getLastReporTime()
|
||||
{
|
||||
return _lastReport;
|
||||
}
|
||||
|
||||
byte getPointsLeft()
|
||||
{
|
||||
return _reportPoints;
|
||||
}
|
||||
|
||||
void setPoints(int points)
|
||||
{
|
||||
_reportPoints = (byte) points;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the info about a reported character
|
||||
*/
|
||||
private final class ReportedCharData
|
||||
{
|
||||
Map<Integer, Long> _reporters;
|
||||
|
||||
ReportedCharData()
|
||||
{
|
||||
_reporters = new HashMap<>();
|
||||
}
|
||||
|
||||
int getReportCount()
|
||||
{
|
||||
return _reporters.size();
|
||||
}
|
||||
|
||||
boolean alredyReportedBy(int objectId)
|
||||
{
|
||||
return _reporters.containsKey(objectId);
|
||||
}
|
||||
|
||||
void addReporter(int objectId, long reportTime)
|
||||
{
|
||||
_reporters.put(objectId, reportTime);
|
||||
}
|
||||
|
||||
boolean reportedBySameClan(L2Clan clan)
|
||||
{
|
||||
if (clan == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int reporterId : _reporters.keySet())
|
||||
{
|
||||
if (clan.isMember(reporterId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SAX loader to parse /config/BotReportPunishments.xml file
|
||||
*/
|
||||
private final class PunishmentsLoader extends DefaultHandler
|
||||
{
|
||||
PunishmentsLoader()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attr)
|
||||
{
|
||||
if (!qName.equals("punishment"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int reportCount = -1, skillId = -1, skillLevel = 1, sysMessage = -1;
|
||||
try
|
||||
{
|
||||
reportCount = Integer.parseInt(attr.getValue("neededReportCount"));
|
||||
skillId = Integer.parseInt(attr.getValue("skillId"));
|
||||
final String level = attr.getValue("skillLevel");
|
||||
final String systemMessageId = attr.getValue("sysMessageId");
|
||||
if (level != null)
|
||||
{
|
||||
skillLevel = Integer.parseInt(level);
|
||||
}
|
||||
|
||||
if (systemMessageId != null)
|
||||
{
|
||||
sysMessage = Integer.parseInt(systemMessageId);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
addPunishment(reportCount, skillId, skillLevel, sysMessage);
|
||||
}
|
||||
}
|
||||
|
||||
class PunishHolder
|
||||
{
|
||||
final Skill _punish;
|
||||
final int _systemMessageId;
|
||||
|
||||
PunishHolder(Skill sk, int sysMsg)
|
||||
{
|
||||
_punish = sk;
|
||||
_systemMessageId = sysMsg;
|
||||
}
|
||||
}
|
||||
|
||||
class ResetPointTask implements Runnable
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
resetPointsAndSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SingletonHolder
|
||||
{
|
||||
static final BotReportTable INSTANCE = new BotReportTable();
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.l2jmobius.gameserver.script.DateRange;
|
||||
import com.l2jmobius.gameserver.script.EventDrop;
|
||||
|
||||
/**
|
||||
* This class manage drop of Special Events created by GM for a defined period.<br>
|
||||
* During a Special Event all L2Attackable can drop extra Items.<br>
|
||||
* Those extra Items are defined in the table <b>allNpcDateDrops</b>.<br>
|
||||
* Each Special Event has a start and end date to stop to drop extra Items automatically.
|
||||
*/
|
||||
public class EventDroplist
|
||||
{
|
||||
/**
|
||||
* The table containing all DataDrop object
|
||||
*/
|
||||
private static final List<DateDrop> ALL_NPC_DATE_DROPS = new ArrayList<>();
|
||||
|
||||
public static class DateDrop
|
||||
{
|
||||
protected final DateRange _dateRange;
|
||||
private final EventDrop _eventDrop;
|
||||
|
||||
public DateDrop(DateRange dateRange, EventDrop eventDrop)
|
||||
{
|
||||
_dateRange = dateRange;
|
||||
_eventDrop = eventDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the _eventDrop
|
||||
*/
|
||||
public EventDrop getEventDrop()
|
||||
{
|
||||
return _eventDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the _dateRange
|
||||
*/
|
||||
public DateRange getDateRange()
|
||||
{
|
||||
return _dateRange;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and Init a new DateDrop then add it to the allNpcDateDrops of EventDroplist .
|
||||
* @param itemIdList The table containing all item identifier of this DateDrop
|
||||
* @param count The table containing min and max value of this DateDrop
|
||||
* @param chance The chance to obtain this drop
|
||||
* @param dateRange The DateRange object to add to this DateDrop
|
||||
*/
|
||||
public void addGlobalDrop(int[] itemIdList, int[] count, int chance, DateRange dateRange)
|
||||
{
|
||||
ALL_NPC_DATE_DROPS.add(new DateDrop(dateRange, new EventDrop(itemIdList, count[0], count[1], chance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemId the item Id for the drop
|
||||
* @param min the minimum drop count
|
||||
* @param max the maximum drop count
|
||||
* @param chance the drop chance
|
||||
* @param dateRange the event drop rate range
|
||||
*/
|
||||
public void addGlobalDrop(int itemId, long min, long max, int chance, DateRange dateRange)
|
||||
{
|
||||
ALL_NPC_DATE_DROPS.add(new DateDrop(dateRange, new EventDrop(itemId, min, max, chance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event drop for a given date range.
|
||||
* @param dateRange the date range.
|
||||
* @param eventDrop the event drop.
|
||||
*/
|
||||
public void addGlobalDrop(DateRange dateRange, EventDrop eventDrop)
|
||||
{
|
||||
ALL_NPC_DATE_DROPS.add(new DateDrop(dateRange, eventDrop));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all DateDrop of EventDroplist allNpcDateDrops within the date range.
|
||||
*/
|
||||
public List<DateDrop> getAllDrops()
|
||||
{
|
||||
final List<DateDrop> list = new ArrayList<>();
|
||||
final Date currentDate = new Date();
|
||||
for (DateDrop drop : ALL_NPC_DATE_DROPS)
|
||||
{
|
||||
if (drop._dateRange.isWithinRange(currentDate))
|
||||
{
|
||||
list.add(drop);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static EventDroplist getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final EventDroplist _instance = new EventDroplist();
|
||||
}
|
||||
}
|
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import static com.l2jmobius.gameserver.model.itemcontainer.Inventory.ADENA_ID;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.l2jmobius.Config;
|
||||
import com.l2jmobius.commons.database.DatabaseFactory;
|
||||
import com.l2jmobius.gameserver.ThreadPoolManager;
|
||||
import com.l2jmobius.gameserver.data.xml.impl.EnchantItemBonusData;
|
||||
import com.l2jmobius.gameserver.engines.DocumentEngine;
|
||||
import com.l2jmobius.gameserver.enums.ItemLocation;
|
||||
import com.l2jmobius.gameserver.idfactory.IdFactory;
|
||||
import com.l2jmobius.gameserver.model.L2Object;
|
||||
import com.l2jmobius.gameserver.model.L2World;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Attackable;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2EventMonsterInstance;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
|
||||
import com.l2jmobius.gameserver.model.events.EventDispatcher;
|
||||
import com.l2jmobius.gameserver.model.events.impl.item.OnItemCreate;
|
||||
import com.l2jmobius.gameserver.model.items.L2Armor;
|
||||
import com.l2jmobius.gameserver.model.items.L2EtcItem;
|
||||
import com.l2jmobius.gameserver.model.items.L2Item;
|
||||
import com.l2jmobius.gameserver.model.items.L2Weapon;
|
||||
import com.l2jmobius.gameserver.model.items.instance.L2ItemInstance;
|
||||
import com.l2jmobius.gameserver.util.GMAudit;
|
||||
|
||||
/**
|
||||
* This class serves as a container for all item templates in the game.
|
||||
*/
|
||||
public class ItemTable
|
||||
{
|
||||
private static Logger LOGGER = Logger.getLogger(ItemTable.class.getName());
|
||||
private static Logger LOGGER_ITEMS = Logger.getLogger("item");
|
||||
|
||||
public static final Map<String, Integer> SLOTS = new HashMap<>();
|
||||
|
||||
private L2Item[] _allTemplates;
|
||||
private final Map<Integer, L2EtcItem> _etcItems = new HashMap<>();
|
||||
private final Map<Integer, L2Armor> _armors = new HashMap<>();
|
||||
private final Map<Integer, L2Weapon> _weapons = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
SLOTS.put("shirt", L2Item.SLOT_UNDERWEAR);
|
||||
SLOTS.put("lbracelet", L2Item.SLOT_L_BRACELET);
|
||||
SLOTS.put("rbracelet", L2Item.SLOT_R_BRACELET);
|
||||
SLOTS.put("talisman", L2Item.SLOT_DECO);
|
||||
SLOTS.put("chest", L2Item.SLOT_CHEST);
|
||||
SLOTS.put("fullarmor", L2Item.SLOT_FULL_ARMOR);
|
||||
SLOTS.put("head", L2Item.SLOT_HEAD);
|
||||
SLOTS.put("hair", L2Item.SLOT_HAIR);
|
||||
SLOTS.put("hairall", L2Item.SLOT_HAIRALL);
|
||||
SLOTS.put("underwear", L2Item.SLOT_UNDERWEAR);
|
||||
SLOTS.put("back", L2Item.SLOT_BACK);
|
||||
SLOTS.put("neck", L2Item.SLOT_NECK);
|
||||
SLOTS.put("legs", L2Item.SLOT_LEGS);
|
||||
SLOTS.put("feet", L2Item.SLOT_FEET);
|
||||
SLOTS.put("gloves", L2Item.SLOT_GLOVES);
|
||||
SLOTS.put("chest,legs", L2Item.SLOT_CHEST | L2Item.SLOT_LEGS);
|
||||
SLOTS.put("belt", L2Item.SLOT_BELT);
|
||||
SLOTS.put("rhand", L2Item.SLOT_R_HAND);
|
||||
SLOTS.put("lhand", L2Item.SLOT_L_HAND);
|
||||
SLOTS.put("lrhand", L2Item.SLOT_LR_HAND);
|
||||
SLOTS.put("rear;lear", L2Item.SLOT_R_EAR | L2Item.SLOT_L_EAR);
|
||||
SLOTS.put("rfinger;lfinger", L2Item.SLOT_R_FINGER | L2Item.SLOT_L_FINGER);
|
||||
SLOTS.put("wolf", L2Item.SLOT_WOLF);
|
||||
SLOTS.put("greatwolf", L2Item.SLOT_GREATWOLF);
|
||||
SLOTS.put("hatchling", L2Item.SLOT_HATCHLING);
|
||||
SLOTS.put("strider", L2Item.SLOT_STRIDER);
|
||||
SLOTS.put("babypet", L2Item.SLOT_BABYPET);
|
||||
SLOTS.put("brooch", L2Item.SLOT_BROOCH);
|
||||
SLOTS.put("brooch_jewel", L2Item.SLOT_BROOCH_JEWEL);
|
||||
SLOTS.put("none", L2Item.SLOT_NONE);
|
||||
|
||||
// retail compatibility
|
||||
SLOTS.put("onepiece", L2Item.SLOT_FULL_ARMOR);
|
||||
SLOTS.put("hair2", L2Item.SLOT_HAIR2);
|
||||
SLOTS.put("dhair", L2Item.SLOT_HAIRALL);
|
||||
SLOTS.put("alldress", L2Item.SLOT_ALLDRESS);
|
||||
SLOTS.put("deco1", L2Item.SLOT_DECO);
|
||||
SLOTS.put("waist", L2Item.SLOT_BELT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a reference to this ItemTable object
|
||||
*/
|
||||
public static ItemTable getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
protected ItemTable()
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
int highest = 0;
|
||||
_armors.clear();
|
||||
_etcItems.clear();
|
||||
_weapons.clear();
|
||||
for (L2Item item : DocumentEngine.getInstance().loadItems())
|
||||
{
|
||||
if (highest < item.getId())
|
||||
{
|
||||
highest = item.getId();
|
||||
}
|
||||
if (item instanceof L2EtcItem)
|
||||
{
|
||||
_etcItems.put(item.getId(), (L2EtcItem) item);
|
||||
}
|
||||
else if (item instanceof L2Armor)
|
||||
{
|
||||
_armors.put(item.getId(), (L2Armor) item);
|
||||
}
|
||||
else
|
||||
{
|
||||
_weapons.put(item.getId(), (L2Weapon) item);
|
||||
}
|
||||
}
|
||||
buildFastLookupTable(highest);
|
||||
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _etcItems.size() + " Etc Items");
|
||||
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _armors.size() + " Armor Items");
|
||||
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + _weapons.size() + " Weapon Items");
|
||||
LOGGER.log(Level.INFO, getClass().getSimpleName() + ": Loaded: " + (_etcItems.size() + _armors.size() + _weapons.size()) + " Items in total.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a variable in which all items are putting in in function of their ID.
|
||||
* @param size
|
||||
*/
|
||||
private void buildFastLookupTable(int size)
|
||||
{
|
||||
// Create a FastLookUp Table called _allTemplates of size : value of the highest item ID
|
||||
LOGGER.info(getClass().getSimpleName() + ": Highest item id used:" + size);
|
||||
_allTemplates = new L2Item[size + 1];
|
||||
|
||||
// Insert armor item in Fast Look Up Table
|
||||
for (L2Armor item : _armors.values())
|
||||
{
|
||||
_allTemplates[item.getId()] = item;
|
||||
}
|
||||
|
||||
// Insert weapon item in Fast Look Up Table
|
||||
for (L2Weapon item : _weapons.values())
|
||||
{
|
||||
_allTemplates[item.getId()] = item;
|
||||
}
|
||||
|
||||
// Insert etcItem item in Fast Look Up Table
|
||||
for (L2EtcItem item : _etcItems.values())
|
||||
{
|
||||
_allTemplates[item.getId()] = item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item corresponding to the item ID
|
||||
* @param id : int designating the item
|
||||
* @return L2Item
|
||||
*/
|
||||
public L2Item getTemplate(int id)
|
||||
{
|
||||
return (id >= _allTemplates.length) || (id < 0) ? null : _allTemplates[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the L2ItemInstance corresponding to the Item Identifier and quantitiy add logs the activity. <B><U> Actions</U> :</B>
|
||||
* <li>Create and Init the L2ItemInstance corresponding to the Item Identifier and quantity</li>
|
||||
* <li>Add the L2ItemInstance object to _allObjects of L2world</li>
|
||||
* <li>Logs Item creation according to log settings</li>
|
||||
* @param process : String Identifier of process triggering this action
|
||||
* @param itemId : int Item Identifier of the item to be created
|
||||
* @param count : int Quantity of items to be created for stackable items
|
||||
* @param actor : L2PcInstance Player requesting the item creation
|
||||
* @param reference : Object Object referencing current action like NPC selling item or previous item in transformation
|
||||
* @return L2ItemInstance corresponding to the new item
|
||||
*/
|
||||
public L2ItemInstance createItem(String process, int itemId, long count, L2PcInstance actor, Object reference)
|
||||
{
|
||||
// Create and Init the L2ItemInstance corresponding to the Item Identifier
|
||||
final L2ItemInstance item = new L2ItemInstance(IdFactory.getInstance().getNextId(), itemId);
|
||||
|
||||
if (process.equalsIgnoreCase("loot"))
|
||||
{
|
||||
ScheduledFuture<?> itemLootShedule;
|
||||
if ((reference instanceof L2Attackable) && ((L2Attackable) reference).isRaid()) // loot privilege for raids
|
||||
{
|
||||
if ((((L2Attackable) reference).getFirstCommandChannelAttacked() != null) && !Config.AUTO_LOOT_RAIDS)
|
||||
{
|
||||
item.setOwnerId(((L2Attackable) reference).getFirstCommandChannelAttacked().getLeaderObjectId());
|
||||
itemLootShedule = ThreadPoolManager.getInstance().scheduleGeneral(new ResetOwner(item), Config.LOOT_RAIDS_PRIVILEGE_INTERVAL);
|
||||
item.setItemLootShedule(itemLootShedule);
|
||||
}
|
||||
}
|
||||
else if (!Config.AUTO_LOOT || ((reference instanceof L2EventMonsterInstance) && ((L2EventMonsterInstance) reference).eventDropOnGround()))
|
||||
{
|
||||
item.setOwnerId(actor.getObjectId());
|
||||
itemLootShedule = ThreadPoolManager.getInstance().scheduleGeneral(new ResetOwner(item), 15000);
|
||||
item.setItemLootShedule(itemLootShedule);
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.DEBUG)
|
||||
{
|
||||
LOGGER.fine(getClass().getSimpleName() + ": Item created oid:" + item.getObjectId() + " itemid:" + itemId);
|
||||
}
|
||||
|
||||
// Add the L2ItemInstance object to _allObjects of L2world
|
||||
L2World.getInstance().storeObject(item);
|
||||
|
||||
// Set Item parameters
|
||||
if (item.isStackable() && (count > 1))
|
||||
{
|
||||
item.setCount(count);
|
||||
}
|
||||
|
||||
if (Config.LOG_ITEMS && !process.equals("Reset") && (!Config.LOG_ITEMS_SMALL_LOG || (Config.LOG_ITEMS_SMALL_LOG && (item.isEquipable() || (item.getId() == ADENA_ID)))))
|
||||
{
|
||||
final LogRecord record = new LogRecord(Level.INFO, "CREATE:" + process);
|
||||
record.setLoggerName("item");
|
||||
record.setParameters(new Object[]
|
||||
{
|
||||
item,
|
||||
actor,
|
||||
reference
|
||||
});
|
||||
LOGGER_ITEMS.log(record);
|
||||
}
|
||||
|
||||
if ((actor != null) && actor.isGM())
|
||||
{
|
||||
String referenceName = "no-reference";
|
||||
if (reference instanceof L2Object)
|
||||
{
|
||||
referenceName = ((L2Object) reference).getName() != null ? ((L2Object) reference).getName() : "no-name";
|
||||
}
|
||||
else if (reference instanceof String)
|
||||
{
|
||||
referenceName = (String) reference;
|
||||
}
|
||||
if (Config.GMAUDIT)
|
||||
{
|
||||
GMAudit.auditGMAction(actor.getName() + " [" + actor.getObjectId() + "]", process + "(id: " + itemId + " count: " + count + " name: " + item.getItemName() + " objId: " + item.getObjectId() + ")", actor.getTarget() != null ? actor.getTarget().getName() : "no-target", "L2Object referencing this action is: " + referenceName);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify to scripts
|
||||
EventDispatcher.getInstance().notifyEventAsync(new OnItemCreate(process, item, actor, reference), item.getItem());
|
||||
return item;
|
||||
}
|
||||
|
||||
public L2ItemInstance createItem(String process, int itemId, int count, L2PcInstance actor)
|
||||
{
|
||||
return createItem(process, itemId, count, actor, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the L2ItemInstance.<br>
|
||||
* <B><U> Actions</U> :</B>
|
||||
* <ul>
|
||||
* <li>Sets L2ItemInstance parameters to be unusable</li>
|
||||
* <li>Removes the L2ItemInstance object to _allObjects of L2world</li>
|
||||
* <li>Logs Item deletion according to log settings</li>
|
||||
* </ul>
|
||||
* @param process a string identifier of process triggering this action.
|
||||
* @param item the item instance to be destroyed.
|
||||
* @param actor the player requesting the item destroy.
|
||||
* @param reference the object referencing current action like NPC selling item or previous item in transformation.
|
||||
*/
|
||||
public void destroyItem(String process, L2ItemInstance item, L2PcInstance actor, Object reference)
|
||||
{
|
||||
synchronized (item)
|
||||
{
|
||||
final long old = item.getCount();
|
||||
item.setCount(0);
|
||||
item.setOwnerId(0);
|
||||
item.setItemLocation(ItemLocation.VOID);
|
||||
item.setLastChange(L2ItemInstance.REMOVED);
|
||||
|
||||
L2World.getInstance().removeObject(item);
|
||||
IdFactory.getInstance().releaseId(item.getObjectId());
|
||||
|
||||
if (Config.LOG_ITEMS && (!Config.LOG_ITEMS_SMALL_LOG || (Config.LOG_ITEMS_SMALL_LOG && (item.isEquipable() || (item.getId() == ADENA_ID)))))
|
||||
{
|
||||
final LogRecord record = new LogRecord(Level.INFO, "DELETE:" + process);
|
||||
record.setLoggerName("item");
|
||||
record.setParameters(new Object[]
|
||||
{
|
||||
item,
|
||||
"PrevCount(" + old + ")",
|
||||
actor,
|
||||
reference
|
||||
});
|
||||
LOGGER_ITEMS.log(record);
|
||||
}
|
||||
|
||||
if ((actor != null) && actor.isGM())
|
||||
{
|
||||
String referenceName = "no-reference";
|
||||
if (reference instanceof L2Object)
|
||||
{
|
||||
referenceName = ((L2Object) reference).getName() != null ? ((L2Object) reference).getName() : "no-name";
|
||||
}
|
||||
else if (reference instanceof String)
|
||||
{
|
||||
referenceName = (String) reference;
|
||||
}
|
||||
if (Config.GMAUDIT)
|
||||
{
|
||||
GMAudit.auditGMAction(actor.getName() + " [" + actor.getObjectId() + "]", process + "(id: " + item.getId() + " count: " + item.getCount() + " itemObjId: " + item.getObjectId() + ")", actor.getTarget() != null ? actor.getTarget().getName() : "no-target", "L2Object referencing this action is: " + referenceName);
|
||||
}
|
||||
}
|
||||
|
||||
// if it's a pet control item, delete the pet as well
|
||||
if (item.getItem().isPetItem())
|
||||
{
|
||||
try (Connection con = DatabaseFactory.getInstance().getConnection();
|
||||
PreparedStatement statement = con.prepareStatement("DELETE FROM pets WHERE item_obj_id=?"))
|
||||
{
|
||||
// Delete the pet in db
|
||||
statement.setInt(1, item.getObjectId());
|
||||
statement.execute();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, "could not delete pet objectid:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reload()
|
||||
{
|
||||
load();
|
||||
EnchantItemBonusData.getInstance().load();
|
||||
}
|
||||
|
||||
protected static class ResetOwner implements Runnable
|
||||
{
|
||||
L2ItemInstance _item;
|
||||
|
||||
public ResetOwner(L2ItemInstance item)
|
||||
{
|
||||
_item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
_item.setOwnerId(0);
|
||||
_item.setItemLootShedule(null);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Integer> getAllArmorsId()
|
||||
{
|
||||
return _armors.keySet();
|
||||
}
|
||||
|
||||
public Collection<L2Armor> getAllArmors()
|
||||
{
|
||||
return _armors.values();
|
||||
}
|
||||
|
||||
public Set<Integer> getAllWeaponsId()
|
||||
{
|
||||
return _weapons.keySet();
|
||||
}
|
||||
|
||||
public Collection<L2Weapon> getAllWeapons()
|
||||
{
|
||||
return _weapons.values();
|
||||
}
|
||||
|
||||
public Set<Integer> getAllEtcItemsId()
|
||||
{
|
||||
return _etcItems.keySet();
|
||||
}
|
||||
|
||||
public Collection<L2EtcItem> getAllEtcItems()
|
||||
{
|
||||
return _etcItems.values();
|
||||
}
|
||||
|
||||
public L2Item[] getAllItems()
|
||||
{
|
||||
return _allTemplates;
|
||||
}
|
||||
|
||||
public int getArraySize()
|
||||
{
|
||||
return _allTemplates.length;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final ItemTable _instance = new ItemTable();
|
||||
}
|
||||
}
|
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.l2jmobius.Config;
|
||||
import com.l2jmobius.gameserver.InstanceListManager;
|
||||
import com.l2jmobius.gameserver.instancemanager.CastleManager;
|
||||
import com.l2jmobius.gameserver.model.actor.instance.L2MerchantInstance;
|
||||
import com.l2jmobius.gameserver.model.entity.Castle;
|
||||
|
||||
/**
|
||||
* @author KenM
|
||||
*/
|
||||
public class MerchantPriceConfigTable implements InstanceListManager
|
||||
{
|
||||
// Zoey76: TODO: Implement using IXmlReader.
|
||||
private static Logger LOGGER = Logger.getLogger(MerchantPriceConfigTable.class.getName());
|
||||
|
||||
public static MerchantPriceConfigTable getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
private static final String MPCS_FILE = "MerchantPriceConfig.xml";
|
||||
|
||||
private final Map<Integer, MerchantPriceConfig> _mpcs = new HashMap<>();
|
||||
private MerchantPriceConfig _defaultMpc;
|
||||
|
||||
public MerchantPriceConfig getMerchantPriceConfig(L2MerchantInstance npc)
|
||||
{
|
||||
for (MerchantPriceConfig mpc : _mpcs.values())
|
||||
{
|
||||
if ((npc.getWorldRegion() != null) && npc.getWorldRegion().containsZone(mpc.getZoneId()))
|
||||
{
|
||||
return mpc;
|
||||
}
|
||||
}
|
||||
return _defaultMpc;
|
||||
}
|
||||
|
||||
public MerchantPriceConfig getMerchantPriceConfig(int id)
|
||||
{
|
||||
return _mpcs.get(id);
|
||||
}
|
||||
|
||||
public void loadXML() throws SAXException, IOException, ParserConfigurationException
|
||||
{
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setValidating(false);
|
||||
factory.setIgnoringComments(true);
|
||||
final File file = new File(Config.DATAPACK_ROOT + "/" + MPCS_FILE);
|
||||
if (!file.exists())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int defaultPriceConfigId;
|
||||
final Document doc = factory.newDocumentBuilder().parse(file);
|
||||
|
||||
Node n = doc.getDocumentElement();
|
||||
final Node dpcNode = n.getAttributes().getNamedItem("defaultPriceConfig");
|
||||
if (dpcNode == null)
|
||||
{
|
||||
throw new IllegalStateException("merchantPriceConfig must define an 'defaultPriceConfig'");
|
||||
}
|
||||
defaultPriceConfigId = Integer.parseInt(dpcNode.getNodeValue());
|
||||
|
||||
MerchantPriceConfig mpc;
|
||||
for (n = n.getFirstChild(); n != null; n = n.getNextSibling())
|
||||
{
|
||||
mpc = parseMerchantPriceConfig(n);
|
||||
if (mpc != null)
|
||||
{
|
||||
_mpcs.put(mpc.getId(), mpc);
|
||||
}
|
||||
}
|
||||
|
||||
final MerchantPriceConfig defaultMpc = getMerchantPriceConfig(defaultPriceConfigId);
|
||||
if (defaultMpc == null)
|
||||
{
|
||||
throw new IllegalStateException("'defaultPriceConfig' points to an non-loaded priceConfig");
|
||||
}
|
||||
_defaultMpc = defaultMpc;
|
||||
}
|
||||
|
||||
private MerchantPriceConfig parseMerchantPriceConfig(Node n)
|
||||
{
|
||||
if (!n.getNodeName().equals("priceConfig"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final int id;
|
||||
final int baseTax;
|
||||
int castleId = -1;
|
||||
int zoneId = -1;
|
||||
final String name;
|
||||
|
||||
Node node = n.getAttributes().getNamedItem("id");
|
||||
if (node == null)
|
||||
{
|
||||
throw new IllegalStateException("Must define the priceConfig 'id'");
|
||||
}
|
||||
id = Integer.parseInt(node.getNodeValue());
|
||||
|
||||
node = n.getAttributes().getNamedItem("name");
|
||||
if (node == null)
|
||||
{
|
||||
throw new IllegalStateException("Must define the priceConfig 'name'");
|
||||
}
|
||||
name = node.getNodeValue();
|
||||
|
||||
node = n.getAttributes().getNamedItem("baseTax");
|
||||
if (node == null)
|
||||
{
|
||||
throw new IllegalStateException("Must define the priceConfig 'baseTax'");
|
||||
}
|
||||
baseTax = Integer.parseInt(node.getNodeValue());
|
||||
|
||||
node = n.getAttributes().getNamedItem("castleId");
|
||||
if (node != null)
|
||||
{
|
||||
castleId = Integer.parseInt(node.getNodeValue());
|
||||
}
|
||||
|
||||
node = n.getAttributes().getNamedItem("zoneId");
|
||||
if (node != null)
|
||||
{
|
||||
zoneId = Integer.parseInt(node.getNodeValue());
|
||||
}
|
||||
|
||||
return new MerchantPriceConfig(id, name, baseTax, castleId, zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadInstances()
|
||||
{
|
||||
try
|
||||
{
|
||||
loadXML();
|
||||
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _mpcs.size() + " merchant price configs.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.SEVERE, getClass().getSimpleName() + ": Failed loading MerchantPriceConfigTable. Reason: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateReferences()
|
||||
{
|
||||
for (MerchantPriceConfig mpc : _mpcs.values())
|
||||
{
|
||||
mpc.updateReferences();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateInstances()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @author KenM
|
||||
*/
|
||||
public static final class MerchantPriceConfig
|
||||
{
|
||||
private final int _id;
|
||||
private final String _name;
|
||||
private final int _baseTax;
|
||||
private final int _castleId;
|
||||
private Castle _castle;
|
||||
private final int _zoneId;
|
||||
|
||||
public MerchantPriceConfig(int id, String name, int baseTax, int castleId, int zoneId)
|
||||
{
|
||||
_id = id;
|
||||
_name = name;
|
||||
_baseTax = baseTax;
|
||||
_castleId = castleId;
|
||||
_zoneId = zoneId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the id.
|
||||
*/
|
||||
public int getId()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the name.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the baseTax.
|
||||
*/
|
||||
public int getBaseTax()
|
||||
{
|
||||
return _baseTax;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the baseTax / 100.0.
|
||||
*/
|
||||
public double getBaseTaxRate()
|
||||
{
|
||||
return _baseTax / 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the castle.
|
||||
*/
|
||||
public Castle getCastle()
|
||||
{
|
||||
return _castle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the zoneId.
|
||||
*/
|
||||
public int getZoneId()
|
||||
{
|
||||
return _zoneId;
|
||||
}
|
||||
|
||||
public boolean hasCastle()
|
||||
{
|
||||
return getCastle() != null;
|
||||
}
|
||||
|
||||
public double getCastleTaxRate()
|
||||
{
|
||||
return hasCastle() ? getCastle().getTaxRate() : 0.0;
|
||||
}
|
||||
|
||||
public int getTotalTax()
|
||||
{
|
||||
return hasCastle() ? (getCastle().getTaxPercent() + getBaseTax()) : getBaseTax();
|
||||
}
|
||||
|
||||
public double getTotalTaxRate()
|
||||
{
|
||||
return getTotalTax() / 100.0;
|
||||
}
|
||||
|
||||
public void updateReferences()
|
||||
{
|
||||
if (_castleId > 0)
|
||||
{
|
||||
_castle = CastleManager.getInstance().getCastleById(_castleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final MerchantPriceConfigTable _instance = new MerchantPriceConfigTable();
|
||||
}
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.l2jmobius.gameserver.model.L2Spawn;
|
||||
import com.l2jmobius.gameserver.model.actor.L2Npc;
|
||||
import com.l2jmobius.util.Rnd;
|
||||
|
||||
/**
|
||||
* This class holds parameter, specific to certain NPCs.<br>
|
||||
* It can be either general parameters overridden for certain NPC instance instead of template parameters(aggro range, for example), or some optional parameters, handled by datapack scripts.<br>
|
||||
* @author GKR
|
||||
*/
|
||||
public class NpcPersonalAIData
|
||||
{
|
||||
private final Map<String, Map<String, Integer>> _AIData = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Instantiates a new table.
|
||||
*/
|
||||
protected NpcPersonalAIData()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores data for given spawn.
|
||||
* @param spawnDat spawn to process
|
||||
* @param data Map of AI values
|
||||
*/
|
||||
public void storeData(L2Spawn spawnDat, Map<String, Integer> data)
|
||||
{
|
||||
if ((data == null) || data.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnDat.getName() == null)
|
||||
{
|
||||
spawnDat.setName(Long.toString(Rnd.nextLong()));
|
||||
}
|
||||
|
||||
_AIData.put(spawnDat.getName(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets AI value with given spawnName and paramName
|
||||
* @param spawnName spawn name to check
|
||||
* @param paramName parameter to check
|
||||
* @return value of given parameter for given spawn name
|
||||
*/
|
||||
public int getAIValue(String spawnName, String paramName)
|
||||
{
|
||||
return hasAIValue(spawnName, paramName) ? _AIData.get(spawnName).get(paramName) : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if there is AI value with given spawnName and paramName
|
||||
* @param spawnName spawn name to check
|
||||
* @param paramName parameter name to check
|
||||
* @return {@code true} if parameter paramName is set for spawn spawnName, {@code false} otherwise
|
||||
*/
|
||||
public boolean hasAIValue(String spawnName, String paramName)
|
||||
{
|
||||
return (spawnName != null) && _AIData.containsKey(spawnName) && _AIData.get(spawnName).containsKey(paramName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes npc parameters by specified values.
|
||||
* @param npc NPC to process
|
||||
* @param spawn link to NPC's spawn
|
||||
* @param spawnName name of spawn
|
||||
*/
|
||||
public void initializeNpcParameters(L2Npc npc, L2Spawn spawn, String spawnName)
|
||||
{
|
||||
if (!_AIData.containsKey(spawnName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, Integer> map = _AIData.get(spawnName);
|
||||
|
||||
try
|
||||
{
|
||||
for (String key : map.keySet())
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "disableRandomAnimation":
|
||||
{
|
||||
npc.setRandomAnimationEnabled(map.get(key) == 0);
|
||||
break;
|
||||
}
|
||||
case "disableRandomWalk":
|
||||
{
|
||||
npc.setIsNoRndWalk(map.get(key) == 1);
|
||||
spawn.setIsNoRndWalk(map.get(key) == 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the single instance of NpcTable.
|
||||
* @return single instance of NpcTable
|
||||
*/
|
||||
public static NpcPersonalAIData getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final NpcPersonalAIData _instance = new NpcPersonalAIData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.l2jmobius.Config;
|
||||
import com.l2jmobius.gameserver.data.xml.impl.SkillTreesData;
|
||||
import com.l2jmobius.gameserver.engines.DocumentEngine;
|
||||
import com.l2jmobius.gameserver.model.skills.CommonSkill;
|
||||
import com.l2jmobius.gameserver.model.skills.Skill;
|
||||
|
||||
/**
|
||||
* Skill data.
|
||||
*/
|
||||
public final class SkillData
|
||||
{
|
||||
private static Logger LOGGER = Logger.getLogger(SkillData.class.getName());
|
||||
|
||||
private final Map<Integer, Skill> _skills = new HashMap<>();
|
||||
private final Map<Integer, Integer> _skillMaxLevel = new HashMap<>();
|
||||
private final Set<Integer> _enchantable = new HashSet<>();
|
||||
|
||||
protected SkillData()
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
public void reload()
|
||||
{
|
||||
load();
|
||||
// Reload Skill Tree as well.
|
||||
SkillTreesData.getInstance().load();
|
||||
}
|
||||
|
||||
private void load()
|
||||
{
|
||||
final Map<Integer, Skill> _temp = new HashMap<>();
|
||||
DocumentEngine.getInstance().loadAllSkills(_temp);
|
||||
|
||||
_skills.clear();
|
||||
_skills.putAll(_temp);
|
||||
|
||||
_skillMaxLevel.clear();
|
||||
_enchantable.clear();
|
||||
for (Skill skill : _skills.values())
|
||||
{
|
||||
final int skillId = skill.getId();
|
||||
final int skillLvl = skill.getLevel();
|
||||
if (skillLvl > 99)
|
||||
{
|
||||
if (!_enchantable.contains(skillId))
|
||||
{
|
||||
_enchantable.add(skillId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// only non-enchanted skills
|
||||
if (skillLvl > getMaxLevel(skillId))
|
||||
{
|
||||
_skillMaxLevel.put(skillId, skillLvl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the skill hash
|
||||
* @param skill The L2Skill to be hashed
|
||||
* @return getSkillHashCode(skill.getId(), skill.getLevel())
|
||||
*/
|
||||
public static int getSkillHashCode(Skill skill)
|
||||
{
|
||||
return getSkillHashCode(skill.getId(), skill.getLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Centralized method for easier change of the hashing sys
|
||||
* @param skillId The Skill Id
|
||||
* @param skillLevel The Skill Level
|
||||
* @return The Skill hash number
|
||||
*/
|
||||
public static int getSkillHashCode(int skillId, int skillLevel)
|
||||
{
|
||||
return (skillId * 1031) + skillLevel;
|
||||
}
|
||||
|
||||
public Skill getSkill(int skillId, int level)
|
||||
{
|
||||
final Skill result = _skills.get(getSkillHashCode(skillId, level));
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// skill/level not found, fix for transformation scripts
|
||||
final int maxLvl = getMaxLevel(skillId);
|
||||
// requested level too high
|
||||
if ((maxLvl > 0) && (level > maxLvl))
|
||||
{
|
||||
if (Config.DEBUG)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": call to unexisting skill level id: " + skillId + " requested level: " + level + " max level: " + maxLvl, new Throwable());
|
||||
}
|
||||
return _skills.get(getSkillHashCode(skillId, maxLvl));
|
||||
}
|
||||
|
||||
LOGGER.warning(getClass().getSimpleName() + ": No skill info found for skill id " + skillId + " and skill level " + level + ".");
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getMaxLevel(int skillId)
|
||||
{
|
||||
final Integer maxLevel = _skillMaxLevel.get(skillId);
|
||||
return maxLevel != null ? maxLevel : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the given skill ID correspond to an enchantable skill.
|
||||
* @param skillId the skill ID
|
||||
* @return {@code true} if the skill is enchantable, {@code false} otherwise
|
||||
*/
|
||||
public boolean isEnchantable(int skillId)
|
||||
{
|
||||
return _enchantable.contains(skillId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param addNoble
|
||||
* @param hasCastle
|
||||
* @return an array with siege skills. If addNoble == true, will add also Advanced headquarters.
|
||||
*/
|
||||
public List<Skill> getSiegeSkills(boolean addNoble, boolean hasCastle)
|
||||
{
|
||||
final List<Skill> temp = new LinkedList<>();
|
||||
|
||||
temp.add(_skills.get(SkillData.getSkillHashCode(CommonSkill.IMPRIT_OF_LIGHT.getId(), 1)));
|
||||
temp.add(_skills.get(SkillData.getSkillHashCode(CommonSkill.IMPRIT_OF_DARKNESS.getId(), 1)));
|
||||
|
||||
temp.add(_skills.get(SkillData.getSkillHashCode(247, 1))); // Build Headquarters
|
||||
|
||||
if (addNoble)
|
||||
{
|
||||
temp.add(_skills.get(SkillData.getSkillHashCode(326, 1))); // Build Advanced Headquarters
|
||||
}
|
||||
if (hasCastle)
|
||||
{
|
||||
temp.add(_skills.get(SkillData.getSkillHashCode(844, 1))); // Outpost Construction
|
||||
temp.add(_skills.get(SkillData.getSkillHashCode(845, 1))); // Outpost Demolition
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
public static SkillData getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final SkillData _instance = new SkillData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,528 @@
|
||||
/*
|
||||
* This file is part of the L2J Mobius project.
|
||||
*
|
||||
* 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.l2jmobius.gameserver.datatables;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.Statement;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import com.l2jmobius.Config;
|
||||
import com.l2jmobius.commons.database.DatabaseFactory;
|
||||
import com.l2jmobius.gameserver.data.xml.impl.NpcData;
|
||||
import com.l2jmobius.gameserver.instancemanager.DayNightSpawnManager;
|
||||
import com.l2jmobius.gameserver.instancemanager.ZoneManager;
|
||||
import com.l2jmobius.gameserver.model.L2Spawn;
|
||||
import com.l2jmobius.gameserver.model.StatsSet;
|
||||
import com.l2jmobius.gameserver.model.actor.templates.L2NpcTemplate;
|
||||
import com.l2jmobius.util.data.xml.IXmlReader;
|
||||
|
||||
/**
|
||||
* Spawn data retriever.
|
||||
* @author Zoey76
|
||||
*/
|
||||
public final class SpawnTable implements IXmlReader
|
||||
{
|
||||
private static final Logger LOGGER = Logger.getLogger(SpawnTable.class.getName());
|
||||
// SQL
|
||||
private static final String SELECT_SPAWNS = "SELECT count, npc_templateid, locx, locy, locz, heading, respawn_delay, respawn_random, loc_id, periodOfDay FROM spawnlist";
|
||||
private static final String SELECT_CUSTOM_SPAWNS = "SELECT count, npc_templateid, locx, locy, locz, heading, respawn_delay, respawn_random, loc_id, periodOfDay FROM custom_spawnlist";
|
||||
|
||||
private static final Map<Integer, Set<L2Spawn>> _spawnTable = new ConcurrentHashMap<>();
|
||||
|
||||
private int _xmlSpawnCount = 0;
|
||||
|
||||
/**
|
||||
* Wrapper to load all spawns.
|
||||
*/
|
||||
@Override
|
||||
public void load()
|
||||
{
|
||||
if (Config.ALT_DEV_NO_SPAWNS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fillSpawnTable(false);
|
||||
final int spawnCount = _spawnTable.size();
|
||||
LOGGER.info(getClass().getSimpleName() + ": Loaded " + spawnCount + " npc spawns.");
|
||||
if (Config.CUSTOM_SPAWNLIST_TABLE)
|
||||
{
|
||||
fillSpawnTable(true);
|
||||
LOGGER.info(getClass().getSimpleName() + ": Loaded " + (_spawnTable.size() - spawnCount) + " custom npc spawns.");
|
||||
}
|
||||
|
||||
// Load XML list
|
||||
parseDatapackDirectory("spawnlist", false);
|
||||
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _xmlSpawnCount + " npc spawns from XML.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the template exists and it's spawnable.
|
||||
* @param npcId the NPC ID
|
||||
* @return {@code true} if the NPC ID belongs to an spawnable template, {@code false} otherwise
|
||||
*/
|
||||
private boolean checkTemplate(int npcId)
|
||||
{
|
||||
final L2NpcTemplate npcTemplate = NpcData.getInstance().getTemplate(npcId);
|
||||
if (npcTemplate != null)
|
||||
{
|
||||
return !npcTemplate.isType("L2SiegeGuard") && !npcTemplate.isType("L2RaidBoss") && (Config.ALLOW_CLASS_MASTERS || !npcTemplate.isType("L2ClassMaster"));
|
||||
}
|
||||
LOGGER.warning(getClass().getSimpleName() + ": Data missing in NPC table for ID: " + npcId + ".");
|
||||
// Don't spawn
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseDocument(Document doc)
|
||||
{
|
||||
NamedNodeMap attrs;
|
||||
for (Node list = doc.getFirstChild(); list != null; list = list.getNextSibling())
|
||||
{
|
||||
if (list.getNodeName().equalsIgnoreCase("list"))
|
||||
{
|
||||
attrs = list.getAttributes();
|
||||
// skip disabled spawnlists
|
||||
if (!Boolean.parseBoolean(attrs.getNamedItem("enabled").getNodeValue()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (Node param = list.getFirstChild(); param != null; param = param.getNextSibling())
|
||||
{
|
||||
attrs = param.getAttributes();
|
||||
if (param.getNodeName().equalsIgnoreCase("spawn"))
|
||||
{
|
||||
String territoryName = null;
|
||||
String spawnName = null;
|
||||
Map<String, Integer> map = null;
|
||||
|
||||
// Check, if spawn name specified
|
||||
if (attrs.getNamedItem("name") != null)
|
||||
{
|
||||
spawnName = parseString(attrs, "name");
|
||||
}
|
||||
// Check, if spawn territory specified and exists
|
||||
if ((attrs.getNamedItem("zone") != null) && (ZoneManager.getInstance().getSpawnTerritory(attrs.getNamedItem("zone").getNodeValue()) != null))
|
||||
{
|
||||
territoryName = parseString(attrs, "zone");
|
||||
}
|
||||
|
||||
for (Node npctag = param.getFirstChild(); npctag != null; npctag = npctag.getNextSibling())
|
||||
{
|
||||
attrs = npctag.getAttributes();
|
||||
// Check if there are any AI parameters
|
||||
if (npctag.getNodeName().equalsIgnoreCase("AIData"))
|
||||
{
|
||||
attrs = npctag.getAttributes();
|
||||
if (map == null)
|
||||
{
|
||||
map = new HashMap<>();
|
||||
}
|
||||
for (Node c = npctag.getFirstChild(); c != null; c = c.getNextSibling())
|
||||
{
|
||||
// Skip odd nodes
|
||||
if (c.getNodeName().equals("#text"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int val;
|
||||
switch (c.getNodeName())
|
||||
{
|
||||
case "disableRandomAnimation":
|
||||
case "disableRandomWalk":
|
||||
{
|
||||
val = Boolean.parseBoolean(c.getTextContent()) ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
val = Integer.parseInt(c.getTextContent());
|
||||
}
|
||||
}
|
||||
map.put(c.getNodeName(), val);
|
||||
}
|
||||
}
|
||||
// Check for NPC spawns
|
||||
else if (npctag.getNodeName().equalsIgnoreCase("npc"))
|
||||
{
|
||||
// mandatory
|
||||
final int templateId = parseInteger(attrs, "id");
|
||||
// coordinates are optional, if territory is specified; mandatory otherwise
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int z = 0;
|
||||
|
||||
try
|
||||
{
|
||||
x = parseInteger(attrs, "x");
|
||||
y = parseInteger(attrs, "y");
|
||||
z = parseInteger(attrs, "z");
|
||||
}
|
||||
catch (NullPointerException npe)
|
||||
{
|
||||
// x, y, z can be unspecified, if this spawn is territory based, do nothing
|
||||
}
|
||||
|
||||
if ((x == 0) && (y == 0) && (territoryName == null)) // Both coordinates and zone are unspecified
|
||||
{
|
||||
LOGGER.warning("XML Spawnlist: Spawn could not be initialized, both coordinates and zone are unspecified for ID " + templateId);
|
||||
continue;
|
||||
}
|
||||
|
||||
final StatsSet spawnInfo = new StatsSet();
|
||||
spawnInfo.set("npcTemplateid", templateId);
|
||||
spawnInfo.set("x", x);
|
||||
spawnInfo.set("y", y);
|
||||
spawnInfo.set("z", z);
|
||||
spawnInfo.set("territoryName", territoryName);
|
||||
spawnInfo.set("spawnName", spawnName);
|
||||
|
||||
// trying to read optional parameters
|
||||
if (attrs.getNamedItem("heading") != null)
|
||||
{
|
||||
spawnInfo.set("heading", parseInteger(attrs, "heading"));
|
||||
}
|
||||
|
||||
if (attrs.getNamedItem("count") != null)
|
||||
{
|
||||
spawnInfo.set("count", parseInteger(attrs, "count"));
|
||||
}
|
||||
|
||||
if (attrs.getNamedItem("respawnDelay") != null)
|
||||
{
|
||||
spawnInfo.set("respawnDelay", parseInteger(attrs, "respawnDelay"));
|
||||
}
|
||||
|
||||
if (attrs.getNamedItem("respawnRandom") != null)
|
||||
{
|
||||
spawnInfo.set("respawnRandom", parseInteger(attrs, "respawnRandom"));
|
||||
}
|
||||
|
||||
if (attrs.getNamedItem("periodOfDay") != null)
|
||||
{
|
||||
final String period = attrs.getNamedItem("periodOfDay").getNodeValue();
|
||||
if (period.equalsIgnoreCase("day") || period.equalsIgnoreCase("night"))
|
||||
{
|
||||
spawnInfo.set("periodOfDay", period.equalsIgnoreCase("day") ? 1 : 2);
|
||||
}
|
||||
}
|
||||
|
||||
_xmlSpawnCount += addSpawn(spawnInfo, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves spawn data from database.
|
||||
* @param isCustom if {@code true} the spawns are loaded as custom from custom spawn table
|
||||
* @return the spawn count
|
||||
*/
|
||||
private int fillSpawnTable(boolean isCustom)
|
||||
{
|
||||
int npcSpawnCount = 0;
|
||||
try (Connection con = DatabaseFactory.getInstance().getConnection();
|
||||
Statement s = con.createStatement();
|
||||
ResultSet rs = s.executeQuery(isCustom ? SELECT_CUSTOM_SPAWNS : SELECT_SPAWNS))
|
||||
{
|
||||
while (rs.next())
|
||||
{
|
||||
final StatsSet spawnInfo = new StatsSet();
|
||||
final int npcId = rs.getInt("npc_templateid");
|
||||
|
||||
// Check basic requirements first
|
||||
if (!checkTemplate(npcId))
|
||||
{
|
||||
// Don't spawn
|
||||
continue;
|
||||
}
|
||||
|
||||
spawnInfo.set("npcTemplateid", npcId);
|
||||
spawnInfo.set("count", rs.getInt("count"));
|
||||
spawnInfo.set("x", rs.getInt("locx"));
|
||||
spawnInfo.set("y", rs.getInt("locy"));
|
||||
spawnInfo.set("z", rs.getInt("locz"));
|
||||
spawnInfo.set("heading", rs.getInt("heading"));
|
||||
spawnInfo.set("respawnDelay", rs.getInt("respawn_delay"));
|
||||
spawnInfo.set("respawnRandom", rs.getInt("respawn_random"));
|
||||
spawnInfo.set("locId", rs.getInt("loc_id"));
|
||||
spawnInfo.set("periodOfDay", rs.getInt("periodOfDay"));
|
||||
spawnInfo.set("isCustomSpawn", isCustom);
|
||||
npcSpawnCount += addSpawn(spawnInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Spawn could not be initialized: " + e.getMessage(), e);
|
||||
}
|
||||
return npcSpawnCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates NPC spawn
|
||||
* @param spawnInfo StatsSet of spawn parameters
|
||||
* @param AIData Map of specific AI parameters for this spawn
|
||||
* @return count NPC instances, spawned by this spawn
|
||||
*/
|
||||
private int addSpawn(StatsSet spawnInfo, Map<String, Integer> AIData)
|
||||
{
|
||||
L2Spawn spawnDat;
|
||||
int ret = 0;
|
||||
try
|
||||
{
|
||||
spawnDat = new L2Spawn(spawnInfo.getInt("npcTemplateid"));
|
||||
spawnDat.setAmount(spawnInfo.getInt("count", 1));
|
||||
spawnDat.setX(spawnInfo.getInt("x", 0));
|
||||
spawnDat.setY(spawnInfo.getInt("y", 0));
|
||||
spawnDat.setZ(spawnInfo.getInt("z", 0));
|
||||
spawnDat.setHeading(spawnInfo.getInt("heading", -1));
|
||||
spawnDat.setRespawnDelay(spawnInfo.getInt("respawnDelay", 0), spawnInfo.getInt("respawnRandom", 0));
|
||||
spawnDat.setLocationId(spawnInfo.getInt("locId", 0));
|
||||
final String territoryName = spawnInfo.getString("territoryName", "");
|
||||
final String spawnName = spawnInfo.getString("spawnName", "");
|
||||
spawnDat.setCustom(spawnInfo.getBoolean("isCustomSpawn", false));
|
||||
if (!spawnName.isEmpty())
|
||||
{
|
||||
spawnDat.setName(spawnName);
|
||||
}
|
||||
if (!territoryName.isEmpty())
|
||||
{
|
||||
spawnDat.setSpawnTerritory(ZoneManager.getInstance().getSpawnTerritory(territoryName));
|
||||
}
|
||||
// Register AI Data for this spawn
|
||||
NpcPersonalAIData.getInstance().storeData(spawnDat, AIData);
|
||||
switch (spawnInfo.getInt("periodOfDay", 0))
|
||||
{
|
||||
case 0: // default
|
||||
{
|
||||
ret += spawnDat.init();
|
||||
break;
|
||||
}
|
||||
case 1: // Day
|
||||
{
|
||||
DayNightSpawnManager.getInstance().addDayCreature(spawnDat);
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
case 2: // Night
|
||||
{
|
||||
DayNightSpawnManager.getInstance().addNightCreature(spawnDat);
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addSpawn(spawnDat);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, "Spawn could not be initialized: " + e.getMessage(), e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #addSpawn(StatsSet, Map)}.
|
||||
* @param spawnInfo StatsSet of spawn parameters
|
||||
* @return count NPC instances, spawned by this spawn
|
||||
*/
|
||||
private int addSpawn(StatsSet spawnInfo)
|
||||
{
|
||||
return addSpawn(spawnInfo, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spawn data.
|
||||
* @return the spawn data
|
||||
*/
|
||||
public Map<Integer, Set<L2Spawn>> getSpawnTable()
|
||||
{
|
||||
return _spawnTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spawns for the NPC Id.
|
||||
* @param npcId the NPC Id
|
||||
* @return the spawn set for the given npcId
|
||||
*/
|
||||
public Set<L2Spawn> getSpawns(int npcId)
|
||||
{
|
||||
return _spawnTable.getOrDefault(npcId, Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spawn count for the given NPC ID.
|
||||
* @param npcId the NPC Id
|
||||
* @return the spawn count
|
||||
*/
|
||||
public int getSpawnCount(int npcId)
|
||||
{
|
||||
return getSpawns(npcId).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a spawn for the given NPC ID.
|
||||
* @param npcId the NPC Id
|
||||
* @return a spawn for the given NPC ID or {@code null}
|
||||
*/
|
||||
public L2Spawn findAny(int npcId)
|
||||
{
|
||||
return getSpawns(npcId).stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new spawn to the spawn table.
|
||||
* @param spawn the spawn to add
|
||||
* @param storeInDb if {@code true} it'll be saved in the database
|
||||
*/
|
||||
public void addNewSpawn(L2Spawn spawn, boolean storeInDb)
|
||||
{
|
||||
addSpawn(spawn);
|
||||
|
||||
if (!storeInDb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
final String spawnTable = spawn.isCustom() && Config.CUSTOM_SPAWNLIST_TABLE ? "custom_spawnlist" : "spawnlist";
|
||||
try (Connection con = DatabaseFactory.getInstance().getConnection();
|
||||
PreparedStatement insert = con.prepareStatement("INSERT INTO " + spawnTable + "(count,npc_templateid,locx,locy,locz,heading,respawn_delay,respawn_random,loc_id) values(?,?,?,?,?,?,?,?,?)"))
|
||||
{
|
||||
insert.setInt(1, spawn.getAmount());
|
||||
insert.setInt(2, spawn.getId());
|
||||
insert.setInt(3, spawn.getX());
|
||||
insert.setInt(4, spawn.getY());
|
||||
insert.setInt(5, spawn.getZ());
|
||||
insert.setInt(6, spawn.getHeading());
|
||||
insert.setInt(7, spawn.getRespawnDelay() / 1000);
|
||||
insert.setInt(8, spawn.getRespawnMaxDelay() - spawn.getRespawnMinDelay());
|
||||
insert.setInt(9, spawn.getLocationId());
|
||||
insert.execute();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not store spawn in the DB:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an spawn from the spawn table.
|
||||
* @param spawn the spawn to delete
|
||||
* @param updateDb if {@code true} database will be updated
|
||||
*/
|
||||
public void deleteSpawn(L2Spawn spawn, boolean updateDb)
|
||||
{
|
||||
if (!removeSpawn(spawn))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateDb)
|
||||
{
|
||||
try (Connection con = DatabaseFactory.getInstance().getConnection();
|
||||
PreparedStatement delete = con.prepareStatement("DELETE FROM " + (spawn.isCustom() ? "custom_spawnlist" : "spawnlist") + " WHERE locx=? AND locy=? AND locz=? AND npc_templateid=? AND heading=?"))
|
||||
{
|
||||
delete.setInt(1, spawn.getX());
|
||||
delete.setInt(2, spawn.getY());
|
||||
delete.setInt(3, spawn.getZ());
|
||||
delete.setInt(4, spawn.getId());
|
||||
delete.setInt(5, spawn.getHeading());
|
||||
delete.execute();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Spawn " + spawn + " could not be removed from DB: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a spawn to the spawn set if present, otherwise add a spawn set and add the spawn to the newly created spawn set.
|
||||
* @param spawn the NPC spawn to add
|
||||
*/
|
||||
private void addSpawn(L2Spawn spawn)
|
||||
{
|
||||
_spawnTable.computeIfAbsent(spawn.getId(), k -> ConcurrentHashMap.newKeySet(1)).add(spawn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a spawn from the spawn set, if the spawn set is empty, remove it as well.
|
||||
* @param spawn the NPC spawn to remove
|
||||
* @return {@code true} if the spawn was successfully removed, {@code false} otherwise
|
||||
*/
|
||||
private boolean removeSpawn(L2Spawn spawn)
|
||||
{
|
||||
final Set<L2Spawn> set = _spawnTable.get(spawn.getId());
|
||||
if (set == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean removed = set.remove(spawn);
|
||||
if (set.isEmpty())
|
||||
{
|
||||
_spawnTable.remove(spawn.getId());
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a procedure over all spawns.<br>
|
||||
* <font size="4" color="red">Do not use it!</font>
|
||||
* @param function the function to execute
|
||||
* @return {@code true} if all procedures were executed, {@code false} otherwise
|
||||
*/
|
||||
public boolean forEachSpawn(Function<L2Spawn, Boolean> function)
|
||||
{
|
||||
for (Set<L2Spawn> set : _spawnTable.values())
|
||||
{
|
||||
for (L2Spawn spawn : set)
|
||||
{
|
||||
if (!function.apply(spawn))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static SpawnTable getInstance()
|
||||
{
|
||||
return SingletonHolder._instance;
|
||||
}
|
||||
|
||||
private static class SingletonHolder
|
||||
{
|
||||
protected static final SpawnTable _instance = new SpawnTable();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user