Support for drop groups.
This commit is contained in:
parent
4b2456a2e2
commit
6fc3b9073a
@ -37,6 +37,7 @@ import org.l2jmobius.gameserver.model.actor.Attackable;
|
||||
import org.l2jmobius.gameserver.model.actor.Creature;
|
||||
import org.l2jmobius.gameserver.model.actor.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseInteger(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseInteger(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseInteger(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseInteger(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -649,12 +651,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -673,6 +685,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -685,11 +702,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -721,7 +928,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -776,20 +983,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
|
||||
@ -206,12 +207,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -228,13 +230,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -102,6 +103,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -13,7 +13,67 @@
|
||||
<xs:element name="ai" type="aiType" 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="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="exCrtEffect" type="xs:boolean" 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.actor.templates.NpcTemplate;
|
||||
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.MinionHolder;
|
||||
import org.l2jmobius.gameserver.model.holders.SkillHolder;
|
||||
@ -99,6 +100,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -456,24 +458,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -34,6 +34,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private double _collisionRadiusGrown;
|
||||
@ -588,12 +590,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -612,6 +624,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -624,11 +641,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -660,7 +867,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -715,20 +922,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.network.serverpackets.NpcHtmlMessage;
|
||||
@ -206,12 +207,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -228,13 +230,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -102,6 +103,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -13,7 +13,67 @@
|
||||
<xs:element name="ai" type="aiType" 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="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="exCrtEffect" type="xs:boolean" 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.actor.templates.NpcTemplate;
|
||||
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.MinionHolder;
|
||||
import org.l2jmobius.gameserver.model.holders.SkillHolder;
|
||||
@ -99,6 +100,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -456,24 +458,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -34,6 +34,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private double _collisionRadiusGrown;
|
||||
@ -588,12 +590,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -612,6 +624,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -624,11 +641,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -660,7 +867,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -715,20 +922,82 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
calculatedDrops = new ArrayList<>();
|
||||
}
|
||||
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
|
||||
{
|
||||
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedDrops;
|
||||
}
|
||||
|
||||
/**
|
||||
* All item drop chance calculations are done by this method.
|
||||
* @param dropItem
|
||||
* @param victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -650,12 +652,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -674,6 +686,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -686,11 +703,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -722,7 +929,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -777,7 +984,10 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
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))
|
||||
@ -840,7 +1050,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return calculateDrop(dropItem, victim, killer);
|
||||
return calculateUngroupedDrop(dropItem, victim, killer);
|
||||
}
|
||||
|
||||
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 victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
@ -36,6 +36,7 @@ import org.l2jmobius.gameserver.enums.Race;
|
||||
import org.l2jmobius.gameserver.enums.Sex;
|
||||
import org.l2jmobius.gameserver.model.StatSet;
|
||||
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.ItemHolder;
|
||||
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 Set<Integer> _clans;
|
||||
private Set<Integer> _ignoreClanNpcIds;
|
||||
private List<DropGroupHolder> _dropGroups;
|
||||
private List<DropHolder> _dropListDeath;
|
||||
private List<DropHolder> _dropListSpoil;
|
||||
private float _collisionRadiusGrown;
|
||||
@ -650,12 +652,22 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_ignoreClanNpcIds = ignoreClanNpcIds != null ? Collections.unmodifiableSet(ignoreClanNpcIds) : null;
|
||||
}
|
||||
|
||||
public void removeDropGroups()
|
||||
{
|
||||
_dropGroups = null;
|
||||
}
|
||||
|
||||
public void removeDrops()
|
||||
{
|
||||
_dropListDeath = null;
|
||||
_dropListSpoil = null;
|
||||
}
|
||||
|
||||
public void setDropGroups(List<DropGroupHolder> groups)
|
||||
{
|
||||
_dropGroups = groups;
|
||||
}
|
||||
|
||||
public void addDrop(DropHolder dropHolder)
|
||||
{
|
||||
if (_dropListDeath == null)
|
||||
@ -674,6 +686,11 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
_dropListSpoil.add(dropHolder);
|
||||
}
|
||||
|
||||
public List<DropGroupHolder> getDropGroups()
|
||||
{
|
||||
return _dropGroups;
|
||||
}
|
||||
|
||||
public List<DropHolder> getDropList()
|
||||
{
|
||||
return _dropListDeath;
|
||||
@ -686,11 +703,201 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
|
||||
public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
|
||||
{
|
||||
final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
|
||||
if (dropList == null)
|
||||
if (dropType == DropType.DROP)
|
||||
{
|
||||
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
|
||||
final int levelDifference = victim.getLevel() - killer.getLevel();
|
||||
@ -722,7 +929,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
}
|
||||
|
||||
// calculate chances
|
||||
final ItemHolder drop = calculateDrop(dropItem, victim, killer);
|
||||
final ItemHolder drop = calculateUngroupedDrop(dropItem, victim, killer);
|
||||
if (drop == null)
|
||||
{
|
||||
continue;
|
||||
@ -777,7 +984,10 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
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))
|
||||
@ -840,7 +1050,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return calculateDrop(dropItem, victim, killer);
|
||||
return calculateUngroupedDrop(dropItem, victim, killer);
|
||||
}
|
||||
|
||||
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 victim
|
||||
* @param killer
|
||||
* @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())
|
||||
{
|
||||
|
@ -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.Npc;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -326,12 +327,13 @@ public class NpcViewMod implements IBypassHandler
|
||||
private static String getDropListButtons(Npc npc)
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final List<DropGroupHolder> dropListGroups = npc.getTemplate().getDropGroups();
|
||||
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
|
||||
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>");
|
||||
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>");
|
||||
}
|
||||
@ -348,13 +350,40 @@ public class NpcViewMod implements IBypassHandler
|
||||
|
||||
private void sendNpcDropList(Player player, Npc npc, DropType dropType, int pageValue)
|
||||
{
|
||||
final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
|
||||
if (templateList == null)
|
||||
List<DropHolder> dropList = 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;
|
||||
}
|
||||
|
||||
final List<DropHolder> dropList = new ArrayList<>(templateList);
|
||||
Collections.sort(dropList, (d1, d2) -> Integer.valueOf(d1.getItemId()).compareTo(Integer.valueOf(d2.getItemId())));
|
||||
|
||||
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.model.actor.Player;
|
||||
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.item.ItemTemplate;
|
||||
import org.l2jmobius.gameserver.model.itemcontainer.Inventory;
|
||||
@ -104,6 +105,17 @@ public class DropSearchBoard implements IParseBoardHandler
|
||||
|
||||
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 ->
|
||||
{
|
||||
for (DropHolder dropHolder : npcTemplate.getDropList())
|
||||
|
@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:complexType>
|
||||
<xs:sequence>
|
||||
@ -260,11 +249,81 @@
|
||||
</xs:element>
|
||||
<xs:element name="dropLists" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="drop" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="spoil" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="lucky" type="dropList" minOccurs="0" maxOccurs="1" />
|
||||
</xs:all>
|
||||
<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: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:element>
|
||||
<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.actor.templates.NpcTemplate;
|
||||
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.skill.Skill;
|
||||
|
||||
@ -100,6 +101,7 @@ public class NpcData implements IXmlReader
|
||||
Set<Integer> clans = null;
|
||||
Set<Integer> ignoreClanNpcIds = null;
|
||||
List<DropHolder> dropLists = null;
|
||||
List<DropGroupHolder> dropGroups = null;
|
||||
set.set("id", npcId);
|
||||
set.set("displayId", parseInteger(attrs, "displayId"));
|
||||
set.set("level", parseByte(attrs, "level"));
|
||||
@ -436,24 +438,54 @@ public class NpcData implements IXmlReader
|
||||
|
||||
if (dropType != null)
|
||||
{
|
||||
if (dropLists == null)
|
||||
{
|
||||
dropLists = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
|
||||
{
|
||||
final NamedNodeMap dropAttrs = dropNode.getAttributes();
|
||||
if ("item".equalsIgnoreCase(dropNode.getNodeName()))
|
||||
final String nodeName = 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 (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
|
||||
if (dropGroups == 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
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
template.removeDrops();
|
||||
|
||||
// Drops are sorted by chance (high to low).
|
||||
Collections.sort(dropLists, (d1, d2) -> Double.valueOf(d2.getChance()).compareTo(Double.valueOf(d1.getChance())));
|
||||
for (DropHolder dropHolder : dropLists)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user