Addition of rewards on enchant failure.

Contributed by Index.
This commit is contained in:
MobiusDevelopment
2023-01-10 22:18:46 +00:00
parent 3a96d0d0b4
commit a66dc4b72b
63 changed files with 3162 additions and 59 deletions

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,10 +22,12 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -451,6 +453,16 @@ public class RequestEnchantItem implements ClientPacket
} }
} }
if (resultType == EnchantResultType.FAILURE)
{
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
}
player.sendItemList(); player.sendItemList();
player.broadcastUserInfo(); player.broadcastUserInfo();

View File

@@ -24,9 +24,11 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.holders.ItemHolder; import org.l2jmobius.gameserver.model.holders.ItemHolder;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -307,6 +309,14 @@ public class ExRequestMultiEnchantItemList implements ClientPacket
_result.put(i, "FAIL"); _result.put(i, "FAIL");
} }
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, enchantItem);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
_failureReward.put(_failureReward.size() + 1, destroyReward);
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,10 +22,12 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -451,6 +453,16 @@ public class RequestEnchantItem implements ClientPacket
} }
} }
if (resultType == EnchantResultType.FAILURE)
{
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
}
player.sendItemList(); player.sendItemList();
player.broadcastUserInfo(); player.broadcastUserInfo();

View File

@@ -24,9 +24,11 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.holders.ItemHolder; import org.l2jmobius.gameserver.model.holders.ItemHolder;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -307,6 +309,14 @@ public class ExRequestMultiEnchantItemList implements ClientPacket
_result.put(i, "FAIL"); _result.put(i, "FAIL");
} }
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, enchantItem);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
_failureReward.put(_failureReward.size() + 1, destroyReward);
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -353,6 +355,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -420,6 +424,12 @@ public class RequestEnchantItem implements ClientPacket
player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count)); player.sendPacket(new EnchantResult(EnchantResult.FAIL, crystalId, count));
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -358,6 +360,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -391,6 +395,12 @@ public class RequestEnchantItem implements ClientPacket
return; return;
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
World.getInstance().removeObject(item); World.getInstance().removeObject(item);
int count = 0; int count = 0;

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,11 +22,13 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.enums.UserInfoType; import org.l2jmobius.gameserver.enums.UserInfoType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -358,6 +360,8 @@ public class RequestEnchantItem implements ClientPacket
} }
else else
{ {
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
// enchant failed, destroy item // enchant failed, destroy item
if (player.getInventory().destroyItem("Enchant", item, player, null) == null) if (player.getInventory().destroyItem("Enchant", item, player, null) == null)
{ {
@@ -391,6 +395,12 @@ public class RequestEnchantItem implements ClientPacket
return; return;
} }
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
World.getInstance().removeObject(item); World.getInstance().removeObject(item);
int count = 0; int count = 0;

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,10 +22,12 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -457,6 +459,16 @@ public class RequestEnchantItem implements ClientPacket
} }
} }
if (resultType == EnchantResultType.FAILURE)
{
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
}
player.sendItemList(); player.sendItemList();
player.broadcastUserInfo(); player.broadcastUserInfo();

View File

@@ -24,9 +24,11 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.holders.ItemHolder; import org.l2jmobius.gameserver.model.holders.ItemHolder;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -313,6 +315,14 @@ public class ExRequestMultiEnchantItemList implements ClientPacket
_result.put(i, "FAIL"); _result.put(i, "FAIL");
} }
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, enchantItem);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
_failureReward.put(_failureReward.size() + 1, destroyReward);
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();

View File

@@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd"> <list xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/CrystallizableItems.xsd">
<!-- Templates for generating automatically crystallization data. Chance is multiplied by the amount of crystals the item template has. --> <!-- Templates for generating automatically crystallization data. -->
<itemsOnEnchantFailure>
<weapon>
<item id="91462" enchant="7" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="15" />
<item id="91462" enchant="8" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="18" />
<item id="91462" enchant="9" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="21" />
<item id="91462" enchant="10" chance="100" amountD="4" amountC="7" amountB="8" amountA="12" amountS="25" />
<item id="91462" enchant="11" chance="100" amountD="5" amountC="8" amountB="9" amountA="13" amountS="28" />
<item id="91462" enchant="12" chance="100" amountD="6" amountC="9" amountB="10" amountA="14" amountS="29" />
<item id="91462" enchant="13" chance="100" amountD="7" amountC="12" amountB="13" amountA="19" amountS="30" />
<item id="91462" enchant="14" chance="100" amountD="8" amountC="13" amountB="14" amountA="20" amountS="31" />
<item id="91462" enchant="15" chance="100" amountD="9" amountC="14" amountB="15" amountA="21" amountS="32" />
<item id="91462" enchant="16" chance="100" amountD="10" amountC="17" amountB="18" amountA="26" amountS="33" />
<item id="91462" enchant="17" chance="100" amountD="11" amountC="18" amountB="19" amountA="27" amountS="34" />
<item id="91462" enchant="18" chance="100" amountD="12" amountC="19" amountB="20" amountA="28" amountS="35" />
<item id="91462" enchant="19" chance="100" amountD="14" amountC="25" amountB="28" amountA="35" amountS="36" />
</weapon>
<armor>
<item id="91463" enchant="6" chance="100" amountD="1" amountC="2" amountB="3" amountA="5" amountS="10" />
<item id="91463" enchant="7" chance="100" amountD="2" amountC="3" amountB="4" amountA="6" amountS="15" />
<item id="91463" enchant="8" chance="100" amountD="3" amountC="4" amountB="5" amountA="7" amountS="20" />
<item id="91463" enchant="9" chance="100" amountD="5" amountC="7" amountB="9" amountA="13" amountS="25" />
<item id="91463" enchant="10" chance="100" amountD="6" amountC="8" amountB="10" amountA="14" amountS="27"/>
<item id="91463" enchant="11" chance="100" amountD="7" amountC="9" amountB="11" amountA="16" amountS="28" />
<item id="91463" enchant="12" chance="100" amountD="12" amountC="15" amountB="19" amountA="26" amountS="30" />
</armor>
</itemsOnEnchantFailure>
</list> </list>

View File

@@ -43,6 +43,48 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="itemsOnEnchantFailure" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="armor" maxOccurs="1" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:attribute type="xs:int" name="id" use="required" />
<xs:attribute type="xs:int" name="enchant" use="required" />
<xs:attribute type="xs:float" name="chance" use="required" />
<xs:attribute type="xs:int" name="amountD" use="required" />
<xs:attribute type="xs:int" name="amountC" use="required" />
<xs:attribute type="xs:int" name="amountB" use="required" />
<xs:attribute type="xs:int" name="amountA" use="required" />
<xs:attribute type="xs:int" name="amountS" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>

View File

@@ -31,16 +31,19 @@ import org.w3c.dom.Node;
import org.l2jmobius.commons.util.IXmlReader; import org.l2jmobius.commons.util.IXmlReader;
import org.l2jmobius.gameserver.data.ItemTable; import org.l2jmobius.gameserver.data.ItemTable;
import org.l2jmobius.gameserver.enums.CrystallizationType; import org.l2jmobius.gameserver.enums.CrystallizationType;
import org.l2jmobius.gameserver.model.StatSet;
import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder; import org.l2jmobius.gameserver.model.holders.CrystallizationDataHolder;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder; import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.Armor; import org.l2jmobius.gameserver.model.item.Armor;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.Weapon; import org.l2jmobius.gameserver.model.item.Weapon;
import org.l2jmobius.gameserver.model.item.enchant.RewardItemsOnFailure;
import org.l2jmobius.gameserver.model.item.instance.Item; import org.l2jmobius.gameserver.model.item.instance.Item;
import org.l2jmobius.gameserver.model.item.type.CrystalType; import org.l2jmobius.gameserver.model.item.type.CrystalType;
/** /**
* @author UnAfraid * @author UnAfraid, Index
*/ */
public class ItemCrystallizationData implements IXmlReader public class ItemCrystallizationData implements IXmlReader
{ {
@@ -49,6 +52,9 @@ public class ItemCrystallizationData implements IXmlReader
private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class); private final Map<CrystalType, Map<CrystallizationType, List<ItemChanceHolder>>> _crystallizationTemplates = new EnumMap<>(CrystalType.class);
private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>(); private final Map<Integer, CrystallizationDataHolder> _items = new HashMap<>();
private RewardItemsOnFailure _weaponDestroyGroup = new RewardItemsOnFailure();
private RewardItemsOnFailure _armorDestroyGroup = new RewardItemsOnFailure();
protected ItemCrystallizationData() protected ItemCrystallizationData()
{ {
load(); load();
@@ -63,12 +69,32 @@ public class ItemCrystallizationData implements IXmlReader
_crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class)); _crystallizationTemplates.put(crystalType, new EnumMap<>(CrystallizationType.class));
} }
_items.clear(); _items.clear();
_weaponDestroyGroup = new RewardItemsOnFailure();
_armorDestroyGroup = new RewardItemsOnFailure();
parseDatapackFile("data/CrystallizableItems.xml"); parseDatapackFile("data/CrystallizableItems.xml");
if (_crystallizationTemplates.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _crystallizationTemplates.size() + " crystallization templates.");
}
if (_items.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items."); LOGGER.info(getClass().getSimpleName() + ": Loaded " + _items.size() + " pre-defined crystallizable items.");
}
// Generate remaining data. // Generate remaining data.
generateCrystallizationData(); generateCrystallizationData();
if (_weaponDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _weaponDestroyGroup.size() + " weapon enchant failure rewards.");
}
if (_armorDestroyGroup.size() > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Loaded " + _armorDestroyGroup.size() + " armor enchant failure rewards.");
}
} }
@Override @Override
@@ -128,6 +154,20 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
} }
else if ("itemsOnEnchantFailure".equals(o.getNodeName()))
{
for (Node d = o.getFirstChild(); d != null; d = d.getNextSibling())
{
if ("armor".equalsIgnoreCase(d.getNodeName()))
{
_armorDestroyGroup = getFormedHolder(d);
}
else if ("weapon".equalsIgnoreCase(d.getNodeName()))
{
_weaponDestroyGroup = getFormedHolder(d);
}
}
}
} }
} }
} }
@@ -179,7 +219,11 @@ public class ItemCrystallizationData implements IXmlReader
} }
} }
LOGGER.info(getClass().getSimpleName() + ": Generated " + (_items.size() - previousCount) + " crystallizable items from templates."); final int generated = _items.size() - previousCount;
if (generated > 0)
{
LOGGER.info(getClass().getSimpleName() + ": Generated " + generated + " crystallizable items from templates.");
}
} }
public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType) public List<ItemChanceHolder> getCrystallizationTemplate(CrystalType crystalType, CrystallizationType crystallizationType)
@@ -235,6 +279,49 @@ public class ItemCrystallizationData implements IXmlReader
return result; return result;
} }
private RewardItemsOnFailure getFormedHolder(Node node)
{
final RewardItemsOnFailure holder = new RewardItemsOnFailure();
for (Node z = node.getFirstChild(); z != null; z = z.getNextSibling())
{
if ("item".equals(z.getNodeName()))
{
final StatSet failItems = new StatSet(parseAttributes(z));
final int itemId = failItems.getInt("id");
final int enchantLevel = failItems.getInt("enchant");
final double chance = failItems.getDouble("chance");
for (CrystalType grade : CrystalType.values())
{
final long count = failItems.getLong("amount" + grade.name(), Integer.MIN_VALUE);
if (count == Integer.MIN_VALUE)
{
continue;
}
holder.addItemToHolder(itemId, grade, enchantLevel, count, chance);
}
}
}
return holder;
}
public ItemChanceHolder getItemOnDestroy(Player player, Item item)
{
if ((player == null) || (item == null))
{
return null;
}
final RewardItemsOnFailure holder = item.isWeapon() ? _weaponDestroyGroup : _armorDestroyGroup;
final CrystalType grade = item.getTemplate().getCrystalTypePlus();
if (holder.checkIfRewardUnavailable(grade, item.getEnchantLevel()))
{
return null;
}
return holder.getRewardItem(grade, item.getEnchantLevel());
}
/** /**
* Gets the single instance of ItemCrystalizationData. * Gets the single instance of ItemCrystalizationData.
* @return single instance of ItemCrystalizationData * @return single instance of ItemCrystalizationData

View File

@@ -0,0 +1,82 @@
/*
* 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 org.l2jmobius.gameserver.model.item.enchant;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.type.CrystalType;
/**
* @author Index
*/
public class RewardItemsOnFailure
{
private final Map<CrystalType, Map<Integer, ItemChanceHolder>> _rewards = new EnumMap<>(CrystalType.class);
private int _minEnchantLevel = Integer.MAX_VALUE;
private int _maxEnchantLevel = Integer.MIN_VALUE;
public RewardItemsOnFailure()
{
}
public void addItemToHolder(int itemId, CrystalType grade, int enchantLevel, long count, double chance)
{
final ItemChanceHolder item = new ItemChanceHolder(itemId, chance, count);
_rewards.computeIfAbsent(grade, v -> new HashMap<>()).put(enchantLevel, item);
_minEnchantLevel = Math.min(_minEnchantLevel, enchantLevel);
_maxEnchantLevel = Math.max(_maxEnchantLevel, enchantLevel);
}
public ItemChanceHolder getRewardItem(CrystalType grade, int enchantLevel)
{
return _rewards.getOrDefault(grade, new HashMap<>()).getOrDefault(enchantLevel, null);
}
public boolean checkIfRewardUnavailable(CrystalType grade, int enchantLevel)
{
// reversed available
if (_minEnchantLevel > enchantLevel)
{
return true;
}
if (_maxEnchantLevel < enchantLevel)
{
return true;
}
if (!_rewards.containsKey(grade))
{
return true;
}
return !_rewards.get(grade).containsKey(enchantLevel);
}
public int size()
{
int count = 0;
for (Map<Integer, ItemChanceHolder> rewards : _rewards.values())
{
count += rewards.size();
}
return count;
}
}

View File

@@ -22,10 +22,12 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.enums.ItemSkillType; import org.l2jmobius.gameserver.enums.ItemSkillType;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.item.ItemTemplate; import org.l2jmobius.gameserver.model.item.ItemTemplate;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -457,6 +459,16 @@ public class RequestEnchantItem implements ClientPacket
} }
} }
if (resultType == EnchantResultType.FAILURE)
{
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, item);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
}
player.sendItemList(); player.sendItemList();
player.broadcastUserInfo(); player.broadcastUserInfo();

View File

@@ -24,9 +24,11 @@ import org.l2jmobius.Config;
import org.l2jmobius.commons.network.ReadablePacket; import org.l2jmobius.commons.network.ReadablePacket;
import org.l2jmobius.commons.util.Rnd; import org.l2jmobius.commons.util.Rnd;
import org.l2jmobius.gameserver.data.xml.EnchantItemData; import org.l2jmobius.gameserver.data.xml.EnchantItemData;
import org.l2jmobius.gameserver.data.xml.ItemCrystallizationData;
import org.l2jmobius.gameserver.model.World; import org.l2jmobius.gameserver.model.World;
import org.l2jmobius.gameserver.model.actor.Player; import org.l2jmobius.gameserver.model.actor.Player;
import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest; import org.l2jmobius.gameserver.model.actor.request.EnchantItemRequest;
import org.l2jmobius.gameserver.model.holders.ItemChanceHolder;
import org.l2jmobius.gameserver.model.holders.ItemHolder; import org.l2jmobius.gameserver.model.holders.ItemHolder;
import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType; import org.l2jmobius.gameserver.model.item.enchant.EnchantResultType;
import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll; import org.l2jmobius.gameserver.model.item.enchant.EnchantScroll;
@@ -313,6 +315,14 @@ public class ExRequestMultiEnchantItemList implements ClientPacket
_result.put(i, "FAIL"); _result.put(i, "FAIL");
} }
final ItemChanceHolder destroyReward = ItemCrystallizationData.getInstance().getItemOnDestroy(player, enchantItem);
if ((destroyReward != null) && (Rnd.get(100) < destroyReward.getChance()))
{
_failureReward.put(_failureReward.size() + 1, destroyReward);
player.addItem("Enchant", destroyReward.getId(), destroyReward.getCount(), null, true);
player.sendPacket(new EnchantResult(EnchantResult.FAIL, destroyReward.getId(), (int) destroyReward.getCount()));
}
if (Config.LOG_ITEM_ENCHANTS) if (Config.LOG_ITEM_ENCHANTS)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();