Support for drop groups.
This commit is contained in:
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseInteger(attrs, "level"));
|
set.set("level", parseInteger(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseInteger(attrs, "level"));
|
set.set("level", parseInteger(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseInteger(attrs, "level"));
|
set.set("level", parseInteger(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseInteger(attrs, "level"));
|
set.set("level", parseInteger(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -107,6 +108,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ import org.l2jmobius.gameserver.model.WorldObject;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
|
import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
|
||||||
@@ -206,12 +207,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -228,13 +230,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.model.Spawn;
|
import org.l2jmobius.gameserver.model.Spawn;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -102,6 +103,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -13,7 +13,67 @@
|
|||||||
<xs:element name="ai" type="aiType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="ai" type="aiType" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="collision" type="collisionType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="collision" type="collisionType" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="corpseTime" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
|
<xs:element name="corpseTime" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="dropLists" type="dropListsType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="drop" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
<xs:element name="equipment" type="equipmentType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="equipment" type="equipmentType" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="exCrtEffect" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
<xs:element name="exCrtEffect" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="parameters" type="parametersType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="parameters" type="parametersType" minOccurs="0" maxOccurs="1" />
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import org.l2jmobius.gameserver.enums.DropType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.MinionHolder;
|
import org.l2jmobius.gameserver.model.holders.MinionHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.SkillHolder;
|
import org.l2jmobius.gameserver.model.holders.SkillHolder;
|
||||||
@@ -99,6 +100,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -456,24 +458,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,10 +662,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -98,6 +99,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private double _collisionRadiusGrown;
|
private double _collisionRadiusGrown;
|
||||||
@@ -588,12 +590,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -612,6 +624,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -624,11 +641,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -660,7 +867,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -715,20 +922,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ import org.l2jmobius.gameserver.model.WorldObject;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
|
import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
|
||||||
@@ -206,12 +207,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -228,13 +230,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.model.Spawn;
|
import org.l2jmobius.gameserver.model.Spawn;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -102,6 +103,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -13,7 +13,67 @@
|
|||||||
<xs:element name="ai" type="aiType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="ai" type="aiType" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="collision" type="collisionType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="collision" type="collisionType" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="corpseTime" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
|
<xs:element name="corpseTime" type="xs:nonNegativeInteger" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="dropLists" type="dropListsType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="drop" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
<xs:element name="equipment" type="equipmentType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="equipment" type="equipmentType" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="exCrtEffect" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
<xs:element name="exCrtEffect" type="xs:boolean" minOccurs="0" maxOccurs="1" />
|
||||||
<xs:element name="parameters" type="parametersType" minOccurs="0" maxOccurs="1" />
|
<xs:element name="parameters" type="parametersType" minOccurs="0" maxOccurs="1" />
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import org.l2jmobius.gameserver.enums.DropType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.MinionHolder;
|
import org.l2jmobius.gameserver.model.holders.MinionHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.SkillHolder;
|
import org.l2jmobius.gameserver.model.holders.SkillHolder;
|
||||||
@@ -99,6 +100,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -456,24 +458,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,10 +662,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -98,6 +99,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private double _collisionRadiusGrown;
|
private double _collisionRadiusGrown;
|
||||||
@@ -588,12 +590,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -612,6 +624,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -624,11 +641,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -660,7 +867,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -715,20 +922,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return calculatedDrops;
|
return calculatedDrops;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -108,6 +109,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -650,12 +652,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -674,6 +686,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -686,11 +703,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -722,7 +929,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -777,7 +984,10 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.VIP_SYSTEM_ENABLED && (dropType == DropType.DROP))
|
if (Config.VIP_SYSTEM_ENABLED && (dropType == DropType.DROP))
|
||||||
@@ -840,7 +1050,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return calculateDrop(dropItem, victim, killer);
|
return calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
|
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
|
||||||
@@ -858,13 +1068,72 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
|||||||
import org.l2jmobius.gameserver.enums.Sex;
|
import org.l2jmobius.gameserver.enums.Sex;
|
||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
import org.l2jmobius.gameserver.model.holders.ItemHolder;
|
||||||
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
import org.l2jmobius.gameserver.model.interfaces.IIdentifiable;
|
||||||
@@ -108,6 +109,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
private Map<AISkillScope, List<Skill>> _aiSkillLists;
|
||||||
private Set<Integer> _clans;
|
private Set<Integer> _clans;
|
||||||
private Set<Integer> _ignoreClanNpcIds;
|
private Set<Integer> _ignoreClanNpcIds;
|
||||||
|
private List<DropGroupHolder> _dropGroups;
|
||||||
private List<DropHolder> _dropListDeath;
|
private List<DropHolder> _dropListDeath;
|
||||||
private List<DropHolder> _dropListSpoil;
|
private List<DropHolder> _dropListSpoil;
|
||||||
private float _collisionRadiusGrown;
|
private float _collisionRadiusGrown;
|
||||||
@@ -650,12 +652,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeDropGroups()
|
||||||
|
{
|
||||||
|
_dropGroups = null;
|
||||||
|
}
|
||||||
|
|
||||||
public void removeDrops()
|
public void removeDrops()
|
||||||
{
|
{
|
||||||
_dropListDeath = null;
|
_dropListDeath = null;
|
||||||
_dropListSpoil = null;
|
_dropListSpoil = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDropGroups(List<DropGroupHolder> groups)
|
||||||
|
{
|
||||||
|
_dropGroups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
public void addDrop(DropHolder dropHolder)
|
public void addDrop(DropHolder dropHolder)
|
||||||
{
|
{
|
||||||
if (_dropListDeath == null)
|
if (_dropListDeath == null)
|
||||||
@@ -674,6 +686,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
_dropListSpoil.add(dropHolder);
|
_dropListSpoil.add(dropHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DropGroupHolder> getDropGroups()
|
||||||
|
{
|
||||||
|
return _dropGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public List<DropHolder> getDropList()
|
public List<DropHolder> getDropList()
|
||||||
{
|
{
|
||||||
return _dropListDeath;
|
return _dropListDeath;
|
||||||
@@ -686,11 +703,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
|
|
||||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
if (dropType == DropType.DROP)
|
||||||
if (dropList == null)
|
|
||||||
{
|
{
|
||||||
return null;
|
// calculate group drops
|
||||||
|
List<ItemHolder> groupDrops = null;
|
||||||
|
if (_dropGroups != null)
|
||||||
|
{
|
||||||
|
groupDrops = calculateGroupDrops(victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate ungrouped drops
|
||||||
|
List<ItemHolder> ungroupedDrops = null;
|
||||||
|
if (_dropListDeath != null)
|
||||||
|
{
|
||||||
|
ungroupedDrops = calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return results
|
||||||
|
if ((groupDrops != null) && (ungroupedDrops != null))
|
||||||
|
{
|
||||||
|
groupDrops.addAll(ungroupedDrops);
|
||||||
|
ungroupedDrops.clear();
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (groupDrops != null)
|
||||||
|
{
|
||||||
|
return groupDrops;
|
||||||
|
}
|
||||||
|
if (ungroupedDrops != null)
|
||||||
|
{
|
||||||
|
return ungroupedDrops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if ((dropType == DropType.SPOIL) && (_dropListSpoil != null))
|
||||||
|
{
|
||||||
|
return calculateUngroupedDrops(dropType, victim, killer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no drops
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateGroupDrops(Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
// level difference calculations
|
||||||
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
|
final double levelGapChanceToDropAdena = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
|
||||||
|
|
||||||
|
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
|
||||||
|
List<ItemHolder> calculatedDrops = null;
|
||||||
|
for (DropGroupHolder group : _dropGroups)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groupRate = 1;
|
||||||
|
final List<DropHolder> groupDrops = group.getDropList();
|
||||||
|
for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// chance
|
||||||
|
double rateChance = 1;
|
||||||
|
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
if (itemChance != null)
|
||||||
|
{
|
||||||
|
if (itemChance == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rateChance *= itemChance;
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium chance
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb chance? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid chance? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep lowest to avoid chance by id configuration conflicts
|
||||||
|
groupRate = Math.min(groupRate, rateChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
|
||||||
|
{
|
||||||
|
double totalChance = 0; // total group chance is 100
|
||||||
|
final double dropChance = Rnd.nextDouble() * 100;
|
||||||
|
GROUP_DROP: for (DropHolder dropItem : groupDrops)
|
||||||
|
{
|
||||||
|
if (dropOccurrenceCounter <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if item will drop
|
||||||
|
totalChance += dropItem.getChance();
|
||||||
|
if (dropChance >= totalChance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check level gap that may prevent to drop item
|
||||||
|
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the drop
|
||||||
|
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer);
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
if (group.getChance() < 100)
|
||||||
|
{
|
||||||
|
dropOccurrenceCounter--;
|
||||||
|
}
|
||||||
|
calculatedDrops.add(drop);
|
||||||
|
|
||||||
|
// no more drops from this group
|
||||||
|
break GROUP_DROP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// champion extra drop
|
||||||
|
if (victim.isChampion())
|
||||||
|
{
|
||||||
|
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
|
||||||
|
{
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list
|
||||||
|
if (calculatedDrops == null)
|
||||||
|
{
|
||||||
|
calculatedDrops = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculatedDrops;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ItemHolder> calculateUngroupedDrops(DropType dropType, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||||
|
|
||||||
// level difference calculations
|
// level difference calculations
|
||||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||||
@@ -722,7 +929,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate chances
|
// calculate chances
|
||||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
if (drop == null)
|
if (drop == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -777,7 +984,10 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
calculatedDrops = new ArrayList<>();
|
calculatedDrops = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||||
|
{
|
||||||
|
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.VIP_SYSTEM_ENABLED && (dropType == DropType.DROP))
|
if (Config.VIP_SYSTEM_ENABLED && (dropType == DropType.DROP))
|
||||||
@@ -840,7 +1050,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return calculateDrop(dropItem, victim, killer);
|
return calculateUngroupedDrop(dropItem, victim, killer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
|
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
|
||||||
@@ -858,13 +1068,72 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All item drop chance calculations are done by this method.
|
|
||||||
* @param dropItem
|
* @param dropItem
|
||||||
* @param victim
|
* @param victim
|
||||||
* @param killer
|
* @param killer
|
||||||
* @return ItemHolder
|
* @return ItemHolder
|
||||||
*/
|
*/
|
||||||
private ItemHolder calculateDrop(DropHolder dropItem, Creature victim, Creature killer)
|
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
|
{
|
||||||
|
final int itemId = dropItem.getItemId();
|
||||||
|
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
|
||||||
|
final boolean champion = victim.isChampion();
|
||||||
|
|
||||||
|
// calculate amount
|
||||||
|
double rateAmount = 1;
|
||||||
|
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
if (champion && (itemId == Inventory.ADENA_ID))
|
||||||
|
{
|
||||||
|
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// premium amount
|
||||||
|
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
|
||||||
|
{
|
||||||
|
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
|
||||||
|
}
|
||||||
|
else if (item.hasExImmediateEffect())
|
||||||
|
{
|
||||||
|
// TODO: Premium herb amount? :)
|
||||||
|
}
|
||||||
|
else if (victim.isRaid())
|
||||||
|
{
|
||||||
|
// TODO: Premium raid amount? :)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally
|
||||||
|
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dropItem
|
||||||
|
* @param victim
|
||||||
|
* @param killer
|
||||||
|
* @return ItemHolder
|
||||||
|
*/
|
||||||
|
private ItemHolder calculateUngroupedDrop(DropHolder dropItem, Creature victim, Creature killer)
|
||||||
{
|
{
|
||||||
switch (dropItem.getDropType())
|
switch (dropItem.getDropType())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.holders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mobius
|
||||||
|
*/
|
||||||
|
public class DropGroupHolder
|
||||||
|
{
|
||||||
|
private final List<DropHolder> _dropList = new ArrayList<>();
|
||||||
|
private final double _chance;
|
||||||
|
|
||||||
|
public DropGroupHolder(double chance)
|
||||||
|
{
|
||||||
|
_chance = chance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DropHolder> getDropList()
|
||||||
|
{
|
||||||
|
return _dropList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDrop(DropHolder holder)
|
||||||
|
{
|
||||||
|
_dropList.add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChance()
|
||||||
|
{
|
||||||
|
return _chance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
|||||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
private static String getDropListButtons(Npc npc)
|
private static String getDropListButtons(Npc npc)
|
||||||
{
|
{
|
||||||
final StringBuilder sb = new StringBuilder();
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||||
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
|
||||||
if ((dropListDeath != null) || (dropListSpoil != null))
|
if ((dropListGroups != null) || (dropListDeath != null) || (dropListSpoil != null))
|
||||||
{
|
{
|
||||||
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
|
||||||
if (dropListDeath != null)
|
if ((dropListGroups != null) || (dropListDeath != null))
|
||||||
{
|
{
|
||||||
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
sb.append("<td align=center><button value=\"Show Drop\" width=100 height=25 action=\"bypass NpcViewMod dropList DROP " + npc.getObjectId() + "\" back=\"L2UI_CT1.Button_DF_Calculator_Down\" fore=\"L2UI_CT1.Button_DF_Calculator\"></td>");
|
||||||
}
|
}
|
||||||
@@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
|||||||
|
|
||||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||||
{
|
{
|
||||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
List<DropHolder> dropList = null;
|
||||||
if (templateList == null)
|
if (dropType == DropType.SPOIL)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(npc.getTemplate().getSpoilList());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
final List<DropHolder> drops = npc.getTemplate().getDropList();
|
||||||
|
if (drops != null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>(drops);
|
||||||
|
}
|
||||||
|
final List<DropGroupHolder> dropGroups = npc.getTemplate().getDropGroups();
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
if (dropList == null)
|
||||||
|
{
|
||||||
|
dropList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (DropGroupHolder dropGroup : dropGroups)
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
dropList.add(new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dropList == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
|
||||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||||
|
|
||||||
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.handler.CommunityBoardHandler;
|
|||||||
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
import org.l2jmobius.gameserver.handler.IParseBoardHandler;
|
||||||
import org.l2jmobius.gameserver.model.actor.Player;
|
import org.l2jmobius.gameserver.model.actor.Player;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
import org.l2jmobius.gameserver.model.item.ItemTemplate;
|
||||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||||
@@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
|||||||
|
|
||||||
private void buildDropIndex()
|
private void buildDropIndex()
|
||||||
{
|
{
|
||||||
|
NpcData.getInstance().getTemplates(npc -> npc.getDropGroups() != null).forEach(npcTemplate ->
|
||||||
|
{
|
||||||
|
for (DropGroupHolder dropGroup : npcTemplate.getDropGroups())
|
||||||
|
{
|
||||||
|
final double chance = dropGroup.getChance() / 100;
|
||||||
|
for (DropHolder dropHolder : dropGroup.getDropList())
|
||||||
|
{
|
||||||
|
addToDropList(npcTemplate, new DropHolder(dropHolder.getDropType(), dropHolder.getItemId(), dropHolder.getMin(), dropHolder.getMax(), dropHolder.getChance() * chance));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
|
||||||
{
|
{
|
||||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||||
<xs:complexType name="dropListItem">
|
|
||||||
<xs:attribute name="id" type="xs:positiveInteger" use="required" />
|
|
||||||
<xs:attribute name="min" type="xs:nonNegativeInteger" />
|
|
||||||
<xs:attribute name="max" type="xs:positiveInteger" />
|
|
||||||
<xs:attribute name="chance" type="xs:decimal" />
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:complexType name="dropList">
|
|
||||||
<xs:choice minOccurs="1" maxOccurs="unbounded">
|
|
||||||
<xs:element name="item" type="dropListItem" />
|
|
||||||
</xs:choice>
|
|
||||||
</xs:complexType>
|
|
||||||
<xs:element name="list">
|
<xs:element name="list">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
@@ -260,11 +249,81 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||||
<xs:complexType>
|
<xs:complexType>
|
||||||
<xs:all>
|
<xs:sequence>
|
||||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:element name="drop" minOccurs="0">
|
||||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:complexType>
|
||||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
<xs:sequence>
|
||||||
</xs:all>
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="group" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="required"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="required"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="required"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="spoil" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="lucky" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="item" maxOccurs="unbounded" minOccurs="0">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:simpleContent>
|
||||||
|
<xs:extension base="xs:string">
|
||||||
|
<xs:attribute type="xs:int" name="id" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="min" use="optional"/>
|
||||||
|
<xs:attribute type="xs:long" name="max" use="optional"/>
|
||||||
|
<xs:attribute type="xs:double" name="chance" use="optional"/>
|
||||||
|
</xs:extension>
|
||||||
|
</xs:simpleContent>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
<xs:element name="collision" minOccurs="0" maxOccurs="1">
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.l2jmobius.gameserver.enums.MpRewardType;
|
|||||||
import org.l2jmobius.gameserver.model.StatSet;
|
import org.l2jmobius.gameserver.model.StatSet;
|
||||||
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
import org.l2jmobius.gameserver.model.actor.templates.NpcTemplate;
|
||||||
import org.l2jmobius.gameserver.model.effects.EffectType;
|
import org.l2jmobius.gameserver.model.effects.EffectType;
|
||||||
|
import org.l2jmobius.gameserver.model.holders.DropGroupHolder;
|
||||||
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
import org.l2jmobius.gameserver.model.holders.DropHolder;
|
||||||
import org.l2jmobius.gameserver.model.skill.Skill;
|
import org.l2jmobius.gameserver.model.skill.Skill;
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
|||||||
Set<Integer> clans = null;
|
Set<Integer> clans = null;
|
||||||
Set<Integer> ignoreClanNpcIds = null;
|
Set<Integer> ignoreClanNpcIds = null;
|
||||||
List<DropHolder> dropLists = null;
|
List<DropHolder> dropLists = null;
|
||||||
|
List<DropGroupHolder> dropGroups = null;
|
||||||
set.set("id", npcId);
|
set.set("id", npcId);
|
||||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||||
set.set("level", parseByte(attrs, "level"));
|
set.set("level", parseByte(attrs, "level"));
|
||||||
@@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
|||||||
|
|
||||||
if (dropType != null)
|
if (dropType != null)
|
||||||
{
|
{
|
||||||
if (dropLists == null)
|
|
||||||
{
|
|
||||||
dropLists = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||||
{
|
{
|
||||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
final String nodeName = dropNode.getNodeName();
|
||||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
if (nodeName.equalsIgnoreCase("group"))
|
||||||
{
|
{
|
||||||
final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
|
if (dropGroups == null)
|
||||||
if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
|
||||||
{
|
{
|
||||||
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
|
dropGroups = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final DropGroupHolder group = new DropGroupHolder(parseDouble(dropNode.getAttributes(), "chance"));
|
||||||
|
for (Node groupNode = dropNode.getFirstChild(); groupNode != null; groupNode = groupNode.getNextSibling())
|
||||||
|
{
|
||||||
|
if (groupNode.getNodeName().equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
final NamedNodeMap groupAttrs = groupNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(groupAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
group.addDrop(new DropHolder(dropType, itemId, parseLong(groupAttrs, "min"), parseLong(groupAttrs, "max"), parseDouble(groupAttrs, "chance")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropGroups.add(group);
|
||||||
|
}
|
||||||
|
else if (nodeName.equalsIgnoreCase("item"))
|
||||||
|
{
|
||||||
|
if (dropLists == null)
|
||||||
|
{
|
||||||
|
dropLists = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||||
|
final int itemId = parseInteger(dropAttrs, "id");
|
||||||
|
|
||||||
|
if (ItemTable.getInstance().getTemplate(itemId) == null)
|
||||||
|
{
|
||||||
|
LOGGER.warning("DropListItem: Could not find item with id " + itemId + ".");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dropLists.add(dropItem);
|
dropLists.add(new DropHolder(dropType, itemId, parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,10 +644,21 @@ public class NpcData implements IXmlReader
|
|||||||
template.setClans(clans);
|
template.setClans(clans);
|
||||||
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
template.setIgnoreClanNpcIds(ignoreClanNpcIds);
|
||||||
|
|
||||||
|
// Clean old drop groups.
|
||||||
|
template.removeDropGroups();
|
||||||
|
|
||||||
|
// Set new drop groups.
|
||||||
|
if (dropGroups != null)
|
||||||
|
{
|
||||||
|
template.setDropGroups(dropGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old drop lists.
|
||||||
|
template.removeDrops();
|
||||||
|
|
||||||
|
// Set new drop lists.
|
||||||
if (dropLists != null)
|
if (dropLists != null)
|
||||||
{
|
{
|
||||||
template.removeDrops();
|
|
||||||
|
|
||||||
// Drops are sorted by chance (high to low).
|
// Drops are sorted by chance (high to low).
|
||||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||||
for (DropHolder dropHolder : dropLists)
|
for (DropHolder dropHolder : dropLists)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user