Drop calculation related cleanup.

This commit is contained in:
MobiusDevelopment
2021-10-25 21:06:38 +00:00
parent bd51582cac
commit e71a589d18
107 changed files with 1997 additions and 1790 deletions
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1104,6 +1104,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1141,6 +1142,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1100,6 +1100,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1137,6 +1138,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1100,6 +1100,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1137,6 +1138,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1100,6 +1100,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1137,6 +1138,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1086,6 +1086,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1123,6 +1124,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1086,6 +1086,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1123,6 +1124,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1086,6 +1086,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1123,6 +1124,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1097,6 +1097,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1134,6 +1135,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1097,6 +1097,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1134,6 +1135,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -614,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // Lucky drops are added to normal drops and calculated later case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -1097,6 +1097,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1134,6 +1135,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -204,8 +206,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -226,12 +228,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -102,16 +102,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -632,6 +632,7 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
@@ -722,11 +723,11 @@ public class NpcData implements IXmlReader
public List<NpcTemplate> getTemplates(Predicate<NpcTemplate> filter) public List<NpcTemplate> getTemplates(Predicate<NpcTemplate> filter)
{ {
final List<NpcTemplate> result = new ArrayList<>(); final List<NpcTemplate> result = new ArrayList<>();
for (NpcTemplate template : _npcs.values()) for (NpcTemplate npcTemplate : _npcs.values())
{ {
if (filter.test(template)) if (filter.test(npcTemplate))
{ {
result.add(template); result.add(npcTemplate);
} }
} }
return result; return result;
@@ -1058,6 +1058,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1096,6 +1097,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -607,58 +606,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -670,19 +660,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -196,7 +196,7 @@ public class PlayerTemplate extends CreatureTemplate
/** /**
* @param slotId id of inventory slot to return value * @param slotId id of inventory slot to return value
* @return defence value of charactert for EMPTY given slot * @return defense value of character for EMPTY given slot
*/ */
public int getBaseDefBySlot(int slotId) public int getBaseDefBySlot(int slotId)
{ {
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -204,8 +206,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -226,12 +228,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -102,16 +102,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -632,6 +632,7 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
@@ -722,11 +723,11 @@ public class NpcData implements IXmlReader
public List<NpcTemplate> getTemplates(Predicate<NpcTemplate> filter) public List<NpcTemplate> getTemplates(Predicate<NpcTemplate> filter)
{ {
final List<NpcTemplate> result = new ArrayList<>(); final List<NpcTemplate> result = new ArrayList<>();
for (NpcTemplate template : _npcs.values()) for (NpcTemplate npcTemplate : _npcs.values())
{ {
if (filter.test(template)) if (filter.test(npcTemplate))
{ {
result.add(template); result.add(npcTemplate);
} }
} }
return result; return result;
@@ -1058,6 +1058,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1096,6 +1097,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -607,58 +606,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -670,19 +660,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -196,7 +196,7 @@ public class PlayerTemplate extends CreatureTemplate
/** /**
* @param slotId id of inventory slot to return value * @param slotId id of inventory slot to return value
* @return defence value of charactert for EMPTY given slot * @return defense value of character for EMPTY given slot
*/ */
public int getBaseDefBySlot(int slotId) public int getBaseDefBySlot(int slotId)
{ {
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -421,16 +421,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -440,16 +441,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -461,16 +461,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -624,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -643,9 +634,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -654,7 +643,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1100,6 +1100,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1137,6 +1138,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -669,52 +668,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop = calculateLevelGapChanceToDrop(dropItem, levelDifference); if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -726,19 +722,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -769,7 +782,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
return calculatedDrops; return calculatedDrops;
} }
private void processVipDrops(Collection<ItemHolder> items, Creature victim, Creature killer) private void processVipDrops(List<ItemHolder> items, Creature victim, Creature killer)
{ {
final List<DropHolder> dropList = new ArrayList<>(); final List<DropHolder> dropList = new ArrayList<>();
if (killer.getActingPlayer() != null) if (killer.getActingPlayer() != null)
@@ -808,14 +821,12 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
{ {
return null; return null;
} }
return calculateDrop(dropItem, victim, killer); return calculateDrop(dropItem, victim, killer);
} }
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference) private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
{ {
final double levelGapChanceToDrop; final double levelGapChanceToDrop;
if (dropItem.getItemId() == Inventory.ADENA_ID) if (dropItem.getItemId() == Inventory.ADENA_ID)
{ {
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0); levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -421,16 +421,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -440,16 +441,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -461,16 +461,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -624,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -643,9 +634,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -654,7 +643,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1100,6 +1100,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1137,6 +1138,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -669,52 +668,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop = calculateLevelGapChanceToDrop(dropItem, levelDifference); if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -726,19 +722,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -769,7 +782,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
return calculatedDrops; return calculatedDrops;
} }
private void processVipDrops(Collection<ItemHolder> items, Creature victim, Creature killer) private void processVipDrops(List<ItemHolder> items, Creature victim, Creature killer)
{ {
final List<DropHolder> dropList = new ArrayList<>(); final List<DropHolder> dropList = new ArrayList<>();
if (killer.getActingPlayer() != null) if (killer.getActingPlayer() != null)
@@ -808,14 +821,12 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
{ {
return null; return null;
} }
return calculateDrop(dropItem, victim, killer); return calculateDrop(dropItem, victim, killer);
} }
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference) private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
{ {
final double levelGapChanceToDrop; final double levelGapChanceToDrop;
if (dropItem.getItemId() == Inventory.ADENA_ID) if (dropItem.getItemId() == Inventory.ADENA_ID)
{ {
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0); levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -204,10 +204,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -421,16 +421,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -440,16 +441,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -461,16 +461,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -624,12 +614,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -643,9 +634,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -654,7 +643,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1100,6 +1100,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1137,6 +1138,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -669,52 +668,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop = calculateLevelGapChanceToDrop(dropItem, levelDifference); if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -726,19 +722,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -769,7 +782,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
return calculatedDrops; return calculatedDrops;
} }
private void processVipDrops(Collection<ItemHolder> items, Creature victim, Creature killer) private void processVipDrops(List<ItemHolder> items, Creature victim, Creature killer)
{ {
final List<DropHolder> dropList = new ArrayList<>(); final List<DropHolder> dropList = new ArrayList<>();
if (killer.getActingPlayer() != null) if (killer.getActingPlayer() != null)
@@ -808,14 +821,12 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
{ {
return null; return null;
} }
return calculateDrop(dropItem, victim, killer); return calculateDrop(dropItem, victim, killer);
} }
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference) private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
{ {
final double levelGapChanceToDrop; final double levelGapChanceToDrop;
if (dropItem.getItemId() == Inventory.ADENA_ID) if (dropItem.getItemId() == Inventory.ADENA_ID)
{ {
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0); levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -207,10 +207,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -424,16 +424,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -443,16 +444,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -464,16 +464,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -627,12 +617,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -646,9 +637,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -657,7 +646,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1121,6 +1121,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1158,6 +1159,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -684,52 +683,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop = calculateLevelGapChanceToDrop(dropItem, levelDifference); if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -741,19 +737,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -784,7 +797,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
return calculatedDrops; return calculatedDrops;
} }
private void processVipDrops(Collection<ItemHolder> items, Creature victim, Creature killer) private void processVipDrops(List<ItemHolder> items, Creature victim, Creature killer)
{ {
final List<DropHolder> dropList = new ArrayList<>(); final List<DropHolder> dropList = new ArrayList<>();
if (killer.getActingPlayer() != null) if (killer.getActingPlayer() != null)
@@ -823,14 +836,12 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
{ {
return null; return null;
} }
return calculateDrop(dropItem, victim, killer); return calculateDrop(dropItem, victim, killer);
} }
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference) private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
{ {
final double levelGapChanceToDrop; final double levelGapChanceToDrop;
if (dropItem.getItemId() == Inventory.ADENA_ID) if (dropItem.getItemId() == Inventory.ADENA_ID)
{ {
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0); levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -207,10 +207,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -424,16 +424,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -443,16 +444,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -464,16 +464,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -627,12 +617,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -646,9 +637,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -657,7 +646,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1121,6 +1121,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1158,6 +1159,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -684,52 +683,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop = calculateLevelGapChanceToDrop(dropItem, levelDifference); if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -741,19 +737,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -784,7 +797,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
return calculatedDrops; return calculatedDrops;
} }
private void processVipDrops(Collection<ItemHolder> items, Creature victim, Creature killer) private void processVipDrops(List<ItemHolder> items, Creature victim, Creature killer)
{ {
final List<DropHolder> dropList = new ArrayList<>(); final List<DropHolder> dropList = new ArrayList<>();
if (killer.getActingPlayer() != null) if (killer.getActingPlayer() != null)
@@ -823,14 +836,12 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
{ {
return null; return null;
} }
return calculateDrop(dropItem, victim, killer); return calculateDrop(dropItem, victim, killer);
} }
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference) private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
{ {
final double levelGapChanceToDrop; final double levelGapChanceToDrop;
if (dropItem.getItemId() == Inventory.ADENA_ID) if (dropItem.getItemId() == Inventory.ADENA_ID)
{ {
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0); levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -207,10 +207,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -424,16 +424,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -443,16 +444,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -464,16 +464,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -627,12 +617,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -646,9 +637,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -657,7 +646,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1121,6 +1121,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1158,6 +1159,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -684,52 +683,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop = calculateLevelGapChanceToDrop(dropItem, levelDifference); if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -741,19 +737,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -784,7 +797,7 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
return calculatedDrops; return calculatedDrops;
} }
private void processVipDrops(Collection<ItemHolder> items, Creature victim, Creature killer) private void processVipDrops(List<ItemHolder> items, Creature victim, Creature killer)
{ {
final List<DropHolder> dropList = new ArrayList<>(); final List<DropHolder> dropList = new ArrayList<>();
if (killer.getActingPlayer() != null) if (killer.getActingPlayer() != null)
@@ -823,14 +836,12 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
{ {
return null; return null;
} }
return calculateDrop(dropItem, victim, killer); return calculateDrop(dropItem, victim, killer);
} }
private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference) private double calculateLevelGapChanceToDrop(DropHolder dropItem, int levelDifference)
{ {
final double levelGapChanceToDrop; final double levelGapChanceToDrop;
if (dropItem.getItemId() == Inventory.ADENA_ID) if (dropItem.getItemId() == Inventory.ADENA_ID)
{ {
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0); levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -206,10 +206,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -423,16 +423,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -442,17 +443,16 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); final Item item = ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id"));
final Item item = ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id"));
if (item == null) if (item == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -471,16 +471,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -634,12 +624,13 @@ public class NpcData implements IXmlReader
if (dropLists != null) if (dropLists != null)
{ {
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -653,9 +644,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -664,7 +653,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.
@@ -1104,6 +1104,7 @@ public class Attackable extends Npc
} }
} }
} }
deathItems.clear();
} }
} }
return; return;
@@ -1141,6 +1142,7 @@ public class Attackable extends Npc
broadcastPacket(sm); broadcastPacket(sm);
} }
} }
deathItems.clear();
} }
} }
@@ -17,7 +17,6 @@
package org.l2jmobius.gameserver.model.actor.templates; package org.l2jmobius.gameserver.model.actor.templates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -668,59 +667,49 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
_dropListSpoil.add(dropHolder); _dropListSpoil.add(dropHolder);
} }
public List<DropHolder> getDropList(DropType dropType) public List<DropHolder> getDropList()
{
switch (dropType)
{
case DROP:
case LUCKY: // never happens
{ {
return _dropListDeath; return _dropListDeath;
} }
case SPOIL:
public List<DropHolder> getSpoilList()
{ {
return _dropListSpoil; return _dropListSpoil;
} }
}
return null;
}
public Collection<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer) public List<ItemHolder> calculateDrops(DropType dropType, Creature victim, Creature killer)
{ {
final List<DropHolder> templateList = getDropList(dropType); final List<DropHolder> dropList = dropType == DropType.SPOIL ? _dropListSpoil : _dropListDeath;
if (templateList == null) if (dropList == null)
{ {
return null; return null;
} }
final List<DropHolder> dropList = new ArrayList<>(templateList); // level difference calculations
// randomize drop order
Collections.shuffle(dropList);
final int levelDifference = victim.getLevel() - killer.getLevel(); 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; int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
Collection<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
List<ItemHolder> randomDrops = null;
ItemHolder replacedItem = null;
if (dropOccurrenceCounter > 0)
{
for (DropHolder dropItem : dropList) for (DropHolder dropItem : dropList)
{ {
// check if maximum drop occurrences have been reached // check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally // items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100)) if ((dropOccurrenceCounter == 0) && (dropItem.getChance() < 100) && (randomDrops != null) && (calculatedDrops != null))
{ {
continue; // remove a random existing drop (temporarily if not other item replaces it)
dropOccurrenceCounter++;
replacedItem = randomDrops.remove(Rnd.get(randomDrops.size()));
calculatedDrops.remove(replacedItem);
} }
// check level gap that may prevent drop this item // check level gap that may prevent to drop item
final double levelGapChanceToDrop; if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
if (dropItem.getItemId() == Inventory.ADENA_ID)
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ADENA_MAX_LEVEL_DIFFERENCE, -Config.DROP_ADENA_MIN_LEVEL_DIFFERENCE, Config.DROP_ADENA_MIN_LEVEL_GAP_CHANCE, 100.0);
}
else
{
levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100.0);
}
if ((Rnd.nextDouble() * 100) > levelGapChanceToDrop)
{ {
continue; continue;
} }
@@ -732,19 +721,36 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
continue; continue;
} }
// create list // create lists
if (randomDrops == null)
{
randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
if (dropItem.getChance() < 100) if (dropItem.getChance() < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
randomDrops.add(drop);
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
} }
}
// add temporarily removed item when not replaced
if ((dropOccurrenceCounter > 0) && (replacedItem != null) && (calculatedDrops != null))
{
calculatedDrops.add(replacedItem);
}
// clear random drops
if (randomDrops != null)
{
randomDrops.clear();
randomDrops = null;
}
// champion extra drop // champion extra drop
if (victim.isChampion()) if (victim.isChampion())
@@ -17,6 +17,8 @@
package handlers.bypasshandlers; package handlers.bypasshandlers;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -324,8 +326,8 @@ public class NpcViewMod implements IBypassHandler
private static String getDropListButtons(Npc npc) private static String getDropListButtons(Npc npc)
{ {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final List<DropHolder> dropListDeath = npc.getTemplate().getDropList(DropType.DROP); final List<DropHolder> dropListDeath = npc.getTemplate().getDropList();
final List<DropHolder> dropListSpoil = npc.getTemplate().getDropList(DropType.SPOIL); final List<DropHolder> dropListSpoil = npc.getTemplate().getSpoilList();
if ((dropListDeath != null) || (dropListSpoil != null)) if ((dropListDeath != null) || (dropListSpoil != null))
{ {
sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>"); sb.append("<table width=275 cellpadding=0 cellspacing=0><tr>");
@@ -346,12 +348,15 @@ public class NpcViewMod implements IBypassHandler
private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue) private void sendNpcDropList(PlayerInstance player, Npc npc, DropType dropType, int pageValue)
{ {
final List<DropHolder> dropList = npc.getTemplate().getDropList(dropType); final List<DropHolder> templateList = dropType == DropType.SPOIL ? npc.getTemplate().getSpoilList() : npc.getTemplate().getDropList();
if (dropList == null) if (templateList == null)
{ {
return; 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; int pages = dropList.size() / DROP_LIST_ITEMS_PER_PAGE;
if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size()) if ((DROP_LIST_ITEMS_PER_PAGE * pages) < dropList.size())
{ {
@@ -104,16 +104,16 @@ public class DropSearchBoard implements IParseBoardHandler
private void buildDropIndex() private void buildDropIndex()
{ {
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.DROP) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getDropList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.DROP)) for (DropHolder dropHolder : npcTemplate.getDropList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
}); });
NpcData.getInstance().getTemplates(npc -> npc.getDropList(DropType.SPOIL) != null).forEach(npcTemplate -> NpcData.getInstance().getTemplates(npc -> npc.getSpoilList() != null).forEach(npcTemplate ->
{ {
for (DropHolder dropHolder : npcTemplate.getDropList(DropType.SPOIL)) for (DropHolder dropHolder : npcTemplate.getSpoilList())
{ {
addToDropList(npcTemplate, dropHolder); addToDropList(npcTemplate, dropHolder);
} }
@@ -98,7 +98,7 @@ public class NpcData implements IXmlReader
final StatSet set = new StatSet(new HashMap<>()); final StatSet set = new StatSet(new HashMap<>());
final int npcId = parseInteger(attrs, "id"); final int npcId = parseInteger(attrs, "id");
final int level = parseInteger(attrs, "level", 85); final int level = parseInteger(attrs, "level", 85);
final String type; final String type = parseString(attrs, "type");
Map<String, Object> parameters = null; Map<String, Object> parameters = null;
Map<Integer, Skill> skills = null; Map<Integer, Skill> skills = null;
Set<Integer> clans = null; Set<Integer> clans = null;
@@ -107,7 +107,6 @@ public class NpcData implements IXmlReader
set.set("id", npcId); set.set("id", npcId);
set.set("displayId", parseInteger(attrs, "displayId")); set.set("displayId", parseInteger(attrs, "displayId"));
set.set("level", level); set.set("level", level);
type = parseString(attrs, "type");
set.set("type", type); set.set("type", type);
set.set("name", parseString(attrs, "name")); set.set("name", parseString(attrs, "name"));
set.set("usingServerSideName", parseBoolean(attrs, "usingServerSideName")); set.set("usingServerSideName", parseBoolean(attrs, "usingServerSideName"));
@@ -211,10 +210,10 @@ public class NpcData implements IXmlReader
} }
case "attribute": case "attribute":
{ {
for (Node attribute_node = statsNode.getFirstChild(); attribute_node != null; attribute_node = attribute_node.getNextSibling()) for (Node attributeNode = statsNode.getFirstChild(); attributeNode != null; attributeNode = attributeNode.getNextSibling())
{ {
attrs = attribute_node.getAttributes(); attrs = attributeNode.getAttributes();
switch (attribute_node.getNodeName().toLowerCase()) switch (attributeNode.getNodeName().toLowerCase())
{ {
case "attack": case "attack":
{ {
@@ -428,16 +427,17 @@ public class NpcData implements IXmlReader
} }
case "droplists": case "droplists":
{ {
for (Node drop_lists_node = npcNode.getFirstChild(); drop_lists_node != null; drop_lists_node = drop_lists_node.getNextSibling()) for (Node dropListsNode = npcNode.getFirstChild(); dropListsNode != null; dropListsNode = dropListsNode.getNextSibling())
{ {
DropType dropType = null; DropType dropType = null;
try try
{ {
dropType = Enum.valueOf(DropType.class, drop_lists_node.getNodeName().toUpperCase()); dropType = Enum.valueOf(DropType.class, dropListsNode.getNodeName().toUpperCase());
} }
catch (Exception e) catch (Exception e)
{ {
// Handled bellow.
} }
if (dropType != null) if (dropType != null)
@@ -447,16 +447,15 @@ public class NpcData implements IXmlReader
dropLists = new ArrayList<>(); dropLists = new ArrayList<>();
} }
for (Node drop_node = drop_lists_node.getFirstChild(); drop_node != null; drop_node = drop_node.getNextSibling()) for (Node dropNode = dropListsNode.getFirstChild(); dropNode != null; dropNode = dropNode.getNextSibling())
{ {
final NamedNodeMap drop_attrs = drop_node.getAttributes(); final NamedNodeMap dropAttrs = dropNode.getAttributes();
if ("item".equals(drop_node.getNodeName().toLowerCase())) if ("item".equalsIgnoreCase(dropNode.getNodeName()))
{ {
final double chance = parseDouble(drop_attrs, "chance"); final DropHolder dropItem = new DropHolder(dropType, parseInteger(dropAttrs, "id"), parseLong(dropAttrs, "min"), parseLong(dropAttrs, "max"), parseDouble(dropAttrs, "chance"));
final DropHolder dropItem = new DropHolder(dropType, parseInteger(drop_attrs, "id"), parseLong(drop_attrs, "min"), parseLong(drop_attrs, "max"), dropType == DropType.LUCKY ? chance / 100 : chance); if (ItemTable.getInstance().getTemplate(parseInteger(dropAttrs, "id")) == null)
if (ItemTable.getInstance().getTemplate(parseInteger(drop_attrs, "id")) == null)
{ {
LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(drop_attrs, "id") + "."); LOGGER.warning("DropListItem: Could not find item with id " + parseInteger(dropAttrs, "id") + ".");
} }
else else
{ {
@@ -468,16 +467,6 @@ public class NpcData implements IXmlReader
} }
break; break;
} }
case "extenddrop":
{
final List<Integer> extendDrop = new ArrayList<>();
forEach(npcNode, "id", idNode ->
{
extendDrop.add(Integer.parseInt(idNode.getTextContent()));
});
set.set("extendDrop", extendDrop);
break;
}
case "collision": case "collision":
{ {
for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling()) for (Node collisionNode = npcNode.getFirstChild(); collisionNode != null; collisionNode = collisionNode.getNextSibling())
@@ -643,6 +632,7 @@ public class NpcData implements IXmlReader
dropLists.add(new DropHolder(DropType.DROP, Inventory.LCOIN_ID, Config.LCOIN_MIN_QUANTITY, Config.LCOIN_MAX_QUANTITY, Config.LCOIN_DROP_CHANCE)); dropLists.add(new DropHolder(DropType.DROP, Inventory.LCOIN_ID, Config.LCOIN_MIN_QUANTITY, Config.LCOIN_MAX_QUANTITY, Config.LCOIN_DROP_CHANCE));
} }
Collections.shuffle(dropLists);
for (DropHolder dropHolder : dropLists) for (DropHolder dropHolder : dropLists)
{ {
// Drop materials for random craft configuration. // Drop materials for random craft configuration.
@@ -654,7 +644,7 @@ public class NpcData implements IXmlReader
switch (dropHolder.getDropType()) switch (dropHolder.getDropType())
{ {
case DROP: case DROP:
case LUCKY: // TODO: Luck is added to death drops. case LUCKY: // Lucky drops are added to normal drops and calculated later.
{ {
template.addDrop(dropHolder); template.addDrop(dropHolder);
break; break;
@@ -668,9 +658,7 @@ public class NpcData implements IXmlReader
} }
} }
if (!template.getParameters().getMinionList("Privates").isEmpty()) if (!template.getParameters().getMinionList("Privates").isEmpty() && (template.getParameters().getSet().get("SummonPrivateRate") == null))
{
if (template.getParameters().getSet().get("SummonPrivateRate") == null)
{ {
_masterMonsterIDs.add(template.getId()); _masterMonsterIDs.add(template.getId());
} }
@@ -679,7 +667,6 @@ public class NpcData implements IXmlReader
} }
} }
} }
}
/** /**
* Gets or creates a clan id if it doesnt exists. * Gets or creates a clan id if it doesnt exists.

Some files were not shown because too many files have changed in this diff Show More